Open whitelilis opened 6 years ago
后记:上面那个修改的直接结果是,NN 变得特别慢,原因是 HashMap 这个类用得太多了,到处都是,把它改了之后,所有的 get 操作都要检查是不是被这里调用的,慢也就再所难免了。所以最后还是重启了。
后记 2: 另一个场景用到了。还是要加速下线,每次考虑多少个 block,是由 dfs.namenode.replication.work.multiplier.per.iteration 这个参数乘以集群节点数,得到一个值,跟当前 underReplica 的值两个取最小,然这个乘数默认值是 2。。。。。。。现在硬盘那么多,性能那么好,2 太少了,所以我想把它改大,发现是被 org.apache.hadoop.hdfs.server.blockmanagement.BlockManager 的 computeReplicationWork 方法来调用(其实直接调用的地方是在初始化时,只调用了一次,动态改不了),所以有了下面的脚本
RULE bigger computerReplicationWork to 24000
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWork
IF TRUE
DO $1 = 4000;
ENDRULE
还真好用
后纪3: 最终问题的解决,转了好多弯。上面那个数字改了之后,比如我一次让集群取 4000 个 block 来迁移,但是集群最终会选出来约 2000 个左右,无论我把数据改成多少,最终都是 2000 个左右。查看的脚本如下:
RULE log compute and filtered
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWork
AT EXIT
IF TRUE
DO traceln("wizard " + $1 + "-->" + $!);
ENDRULE
得到的结果如下:
wizard 6000-->1939
wizard 6000-->1931
wizard 6000-->1939
wizard 6000-->1939
wizard 6000-->1911
wizard 6000-->1898
wizard 6000-->1932
wizard 6000-->1921
wizard 6000-->1932
wizard 6000-->1609
wizard 4000-->1827
wizard 4000-->1927
wizard 4000-->1923
wizard 4000-->1910
wizard 4000-->1901
wizard 4000-->1916
wizard 4000-->1934
wizard 4000-->1910
block 都跑哪里去了呢?需要跟踪 computeReplicationWork 这个函数的执行。而它最终是用 return computeReplicationWorkForBlocks(blocksToReplicate) 返回的。所以要跟踪这个函数。 如果是用 btrace,直接用 line =-1 就可以跟踪了,但是我发现个问题,像 greys/btrace/byteman 这类工具,因为基本原理是一样的,多个工具混用时,会有干扰,可以把 jvm 搞死(亲历),所以只好用 byteman 写一个,把关键逻辑的地方用 counter 记数,看看都跑哪里去了。
RULE counter all
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT ENTRY
IF TRUE
DO
createCounter("bcN1351", 0);
createCounter("srcN1365", 0);
createCounter("numbig1382", 0);
createCounter("total1394", 0);
createCounter("targetN1424", 0);
createCounter("targetN1438", 0);
createCounter("big1452", 0);
createCounter("sameRack1465", 0);
ENDRULE
RULE count 1351
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT LINE 1351
IF TRUE
DO incrementCounter("bcN1351")
ENDRULE
RULE count 1365
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT LINE 1365
IF TRUE
DO incrementCounter("srcN1365")
ENDRULE
RULE count 1382
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT LINE 1382
IF TRUE
DO incrementCounter("numbig1382")
ENDRULE
RULE count 1394
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT LINE 1394
IF TRUE
DO incrementCounter("total1394")
ENDRULE
RULE count 1424
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT LINE 1424
IF TRUE
DO incrementCounter("targetN1424")
ENDRULE
RULE count 1438
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT LINE 1438
IF TRUE
DO incrementCounter("targetN1438")
ENDRULE
RULE count 1452
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT LINE 1452
IF TRUE
DO incrementCounter("big1452")
ENDRULE
RULE count 1465
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT LINE 1465
IF TRUE
DO incrementCounter("sameRack1465")
ENDRULE
RULE count end
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWorkForBlocks
AT EXIT
IF TRUE
DO
traceln("output " + $!);
traceln("bcN1351 " + readCounter("bcN1351"));
traceln("srcN1365 " + readCounter("srcN1365"));
traceln("numBig1382 " + readCounter("numbig1382"));
traceln("total1394 " + readCounter("total1394"));
traceln("targetN1424 " + readCounter("targetN1424"));
traceln("targetN1438 " + readCounter("targetN1438"));
traceln("big1452 " + readCounter("big1452"));
traceln("sameR1465 " + readCounter("sameRack1465"));
deleteCounter("bcN1351");
deleteCounter("srcN1365");
deleteCounter("numbig1382");
deleteCounter("total1394");
deleteCounter("targetN1424");
deleteCounter("targetN1438");
deleteCounter("big1452");
deleteCounter("sameRack1465");
ENDRULE
看看结果:
output 1929
bcN1351 0
srcN1365 2071
numBig1382 0
total1394 1929
targetN1424 0
targetN1438 0
big1452 0
sameR1465 0
output 1927
bcN1351 0
srcN1365 2073
numBig1382 0
total1394 1927
targetN1424 0
targetN1438 0
big1452 0
sameR1465 0
output 1929
bcN1351 0
srcN1365 2071
numBig1382 0
total1394 1929
targetN1424 0
targetN1438 0
big1452 0
sameR1465 0
说明都是第 1365 行的 src 为 null 导致的。
srcNode = chooseSourceDatanode(
block, containingNodes, liveReplicaNodes, numReplicas,
priority);
还要再追一下这个函数。。。。。。。。 但是这种脚本写得太无聊了,需要在所有可能跳过正常逻辑,导致返回 null 的地方,监控一下是走的哪里。 所以用 java 写了一个生成 byteman 脚本的程序如下:
public class BtmGenerator {
public static void main(String[] args) {
String c = "org.apache.hadoop.hdfs.server.blockmanagement.BlockManager";
String m = "chooseSourceDatanode";
int[] lines = {1636, 1638, 1640, 1642, 1649, 1654, 1658, 1662, 1665, 1669, 1676, 1679};
// rule init
String baseName = "lines on " + c + ":" + m;
System.out.println("RULE init " + baseName);
System.out.println("CLASS " + c);
System.out.println("METHOD " + m);
System.out.println("IF TRUE");
System.out.println("DO");
for(int line : lines) {
System.out.println("createCounter(\"cc" + line + "\", 0);");
}
System.out.println("ENDRULE");
// rule exit
System.out.println();
System.out.println("RULE exit " + baseName);
System.out.println("CLASS " + c);
System.out.println("METHOD " + m);
System.out.println("AT EXIT");
System.out.println("IF TRUE");
System.out.println("DO");
System.out.println("traceln(\"===================================\");");
for(int line : lines) {
System.out.printf("traceln(\"line %d : \" + readCounter(\"cc%d\"));%n", line, line);
System.out.println("deleteCounter(\"cc" + line + "\");");
}
System.out.println("ENDRULE");
// rule lines
for(int line : lines) {
System.out.println();
System.out.println("RULE line " + line + "@" + baseName);
System.out.println("CLASS " + c);
System.out.println("METHOD " + m);
System.out.println("AT LINE " + line);
System.out.println("IF TRUE");
System.out.println("DO");
System.out.println("incrementCounter(\"cc" + line + "\");");
System.out.println("ENDRULE");
}
}
}
把生成的 byteman 脚本加载, 运行的结果如下:
=================================== 10.10.1.86:50010 ========================
line 1636 : 0
line 1638 : 2
line 1640 : 0
line 1642 : 1
line 1649 : 0
line 1654 : 0
line 1658 : 2
line 1662 : 0
line 1665 : 0
line 1669 : 1
line 1676 : 0
line 1679 : 1
=================================== null ========================
line 1636 : 0
line 1638 : 2
line 1640 : 0
line 1642 : 1
line 1649 : 0
line 1654 : 0
line 1658 : 3
line 1662 : 0
line 1665 : 0
line 1669 : 0
line 1676 : 0
line 1679 : 1
=================================== null ========================
line 1636 : 0
line 1638 : 2
line 1640 : 0
line 1642 : 1
line 1649 : 0
line 1654 : 0
line 1658 : 3
line 1662 : 0
line 1665 : 0
line 1669 : 0
line 1676 : 0
可以看出,当返回为 null 的时候,都是在第 1658 行,循环了 3 次(因为 3 个副本,都被判定为不可以用)。而 1658 行的判断是:
if (node.getNumberOfBlocksToBeReplicated() >= replicationStreamsHardLimit)
{
continue;
}
后者是 namenode 启动时读取的一个参数 “dfs.namenode.replication.max-streams-hard-limit” 默认值是 4. 而这一步的比较中,所有失败的比较,都是前者等于 4,没有更大的值。怎么知道的?当然还是 byteman。 那么解决问题的办法就是,把这个参数调大一些。重启 NN 是个浩大的工程。我查了一下前面那个函数使用的地方,只在这一个地方用,那就简单了,直接让它返回一个比当前 4 小的值就好了。
RULE watch m2
CLASS org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor
METHOD getNumberOfBlocksToBeReplicated
AT EXIT
IF callerEquals("chooseSourceDatanode")
DO return 3;
ENDRULE
这里有个要注意的点, callerEquals 里只写方法名,不用写类名。。。。我写类名怎么也匹配不上。那不同的类,同名函数怎么解决呢?后续再查查。 这个脚本加载之后,再看一下有没有效果:
wizard 5000-->5000
wizard 5000-->5000
wizard 5000-->5000
wizard 5000-->4999
wizard 5000-->5000
wizard 5000-->4998
wizard 5000-->5000
wizard 5000-->4999
wizard 5000-->5000
wizard 5000-->5000
wizard 5000-->5000
wizard 10000-->9997
wizard 10000-->9999
wizard 10000-->9995
wizard 10000-->10000
wizard 10000-->9999
wizard 10000-->9999
wizard 10000-->10000
基本上可以随心所欲了,想改多少改多少。
callerEquals(String name,
boolean includeClass,
boolean includePackage)
后两个参数,默认是 false,所以不能写类名和包名。这就可以解决上面的疑问了。
有图有真相
本以为调整参数就 ok 了,结果调整后又变回了原来的样子,还是慢,同样的方法追踪一下,这次 limit 设置为 8,最大的就成了 8, 所以又开始一次 2000 左右了。 先用 byteman 改了它,再看看为什么。
最终脚本
RULE bigger computerReplicationWork
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWork
IF TRUE
DO $1 = 40000;
ENDRULE
RULE change return
CLASS org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor
METHOD getNumberOfBlocksToBeReplicated
AT EXIT
IF callerEquals("chooseSourceDatanode")
DO return 3;
ENDRULE
RULE log compute and filtered
CLASS org.apache.hadoop.hdfs.server.blockmanagement.BlockManager
METHOD computeReplicationWork
AT EXIT
IF TRUE
DO traceln("wizard " + $1 + "-->" + $!);
ENDRULE
RULE change limit
CLASS org.apache.hadoop.hdfs.server.blockmanagement.DatanodeManager
METHOD handleHeartbeat
AT ENTRY
BIND new_value = $7 + 1300
IF callerEquals("handleHeartbeat") && $this.getDatanode($1).isDecommissionInProgress()
DO
$7 = new_value;
ENDRULE
byteman 是个比 greys 更强大的工具,之前写过几个工具的比较,放下面:
最近看了 byteman 和 sand-box ,发现 byteman有一些优点还是可以借鉴做到 2.0 里的,比如:
但是也有一些不好的点
sand-box 功能强大,但是写一个东西太太太麻烦了:需要编译成 jar。。。。
我这次主要想用它来改一个东西: https://blog.csdn.net/houzhizhen/article/details/54617851 为了让 hadoop 下线节点加速,其实只需要改一行代码。但是对应的是重新编译,重启 NN(这个时间太久了,差不多要用小时计,我们的 image 太大了。)所以想到了 byteman。
对应的脚本为
byteman 这东西还是要多用,好久不用我就忘记了好多。 其实最大的一个坑在于:bminstall.sh -b pid 如果不用 -b 选项,当用到一些高级用法时(比如我尝试要修改的其实是 HashMap 这个 java 自己的类) 会出现 https://qiita.com/opengl-8080/items/e5244ff862219df8b142 (网页是日文的,但是 google 到的文档里,只有它解决了问题,其它都在瞎扯)这个里面提到的错误 java.lang.NoClassDefFoundError: org/jboss/byteman/rule/exception/EarlyReturnException
小结一下使用方法: 1, bminstall.sh -b pid attach 到 pid 上 2,bmsubmit.sh script 加载脚本; 不加参数,可以看使用了哪些脚本 3,bmsubmit.sh -u 去掉所有脚本; bmsubmit.sh -u 脚本 去掉特定的脚本。 还有几乎不会用到的 bmsubmit.sh -y 查看和修改系统参数