whitelilis / whitelilis.github.io

5 stars 0 forks source link

byteman 的一次修改实例: hadoop 下线节点加速 #17

Open whitelilis opened 6 years ago

whitelilis commented 6 years ago

byteman 是个比 greys 更强大的工具,之前写过几个工具的比较,放下面:

最近看了 byteman 和 sand-box ,发现 byteman有一些优点还是可以借鉴做到 2.0 里的,比如:

1,使用 class + method + (at line, after xxx,at invoke xxx 等) 更精确地定位插入点
2,进一步使用 IF 条件,更精确的定位
3,使用直接变量名和 $ 前缀可以直接看变量的值,这个太太太方便了
4,可以扩展,这年代,可扩展是王道啊

但是也有一些不好的点

1,自己定义的一套语言,不是 java原生,不如 btrace 好用
2,直接输出到屏幕,没搞懂,我现在直接打到系统错误里

sand-box 功能强大,但是写一个东西太太太麻烦了:需要编译成 jar。。。。

我这次主要想用它来改一个东西: https://blog.csdn.net/houzhizhen/article/details/54617851 为了让 hadoop 下线节点加速,其实只需要改一行代码。但是对应的是重新编译,重启 NN(这个时间太久了,差不多要用小时计,我们的 image 太大了。)所以想到了 byteman。

对应的脚本为

#location specifier example
RULE change to zero to speed replica
CLASS java.util.HashMap
METHOD get
AT EXIT
IF callerEquals("org.apache.hadoop.hdfs.server.blockmanagement.UnderReplicatedBlocks.chooseUnderReplicatedBlocks", true)
DO return 0;
ENDRULE

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 查看和修改系统参数

whitelilis commented 6 years ago

后记:上面那个修改的直接结果是,NN 变得特别慢,原因是 HashMap 这个类用得太多了,到处都是,把它改了之后,所有的 get 操作都要检查是不是被这里调用的,慢也就再所难免了。所以最后还是重启了。

whitelilis commented 6 years ago

后记 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

还真好用

whitelilis commented 6 years ago

后纪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

基本上可以随心所欲了,想改多少改多少。

whitelilis commented 6 years ago
callerEquals(String name,
                              boolean includeClass,
                              boolean includePackage)

后两个参数,默认是 false,所以不能写类名和包名。这就可以解决上面的疑问了。

whitelilis commented 6 years ago

有图有真相 default

whitelilis commented 6 years ago

本以为调整参数就 ok 了,结果调整后又变回了原来的样子,还是慢,同样的方法追踪一下,这次 limit 设置为 8,最大的就成了 8, 所以又开始一次 2000 左右了。 先用 byteman 改了它,再看看为什么。

whitelilis commented 6 years ago

最终脚本

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