Hadoop

阿里云ECS上Hadoop HDFS的简单性能测试

在阿里云的ECS上部署了Hadoop,做了下HDFS的简单性能测试,记录如下,性能差距比较大。

使用的阿里云ECS配置如下:

参数名 参数值
Region 青岛
CPU 1核
内存 512MB
实例规格 ecs.t1.xsmall
系统盘 20G
操作系统 CentOS 6.3 64位

Hadoop HDFS的配置如下:

参数名 参数值
HDFS版本 社区2.4.0
集群ECS台数 6台
JVM堆大小 -Xmx400m
NameNode 2台ECS
JournalNode 1台ECS
DataNode 3台ECS

使用了TestDFSIO在同一个网段的另外一台独立的ECS上做多线程的简单写入和读取速率测试,结果如下(3次测试的平均结果):

测试类型 并发数 每个线程的写入大小 速率
写入 10 1GB 24.01MB/s
读取 10 1GB 40.55MB/s

虽然是最低配置,但由于机器上没有跑任何额外程序,Hadoop也只启动了HDFS,且测试程序是在另外一台ECS跑,所以正常应该是能跑慢网卡。

由于DataNode数据为3,所以这个速率可以近似认为是单台DataNode写云磁盘的速率,瓶颈应该还是在云磁盘。

用perf给Hadoop画CPU火焰图和IO热力图

perf自带的分析结果查看方式主要是tui,这种查看方式是在终端下进行查看。Brendan Gregg大神写了几个Perl脚本,可以将perf的结果转换成更直观的火焰图和热力图,本文以Hadoop的HDFS为例介绍如何以更直观的方式查看perf的分析结果。

1. 安装aliperf和taobao-jdk

1
2
sudo yum install aliperf -btest -y
sudo yum install taobao-jdk -y

因为要分析的是Java程序,只有aliperf和taobao-jdk配合才能解析出JIT符号,不然查看perf的分析结果时,看不到Java代码中对应的方法和类。

2. 修改JVM启动参数

本文用到的Hadoop是社区的trunk版本,要在${HADOOP_HOME}/etc/hadoop-env.sh修改${HADOOP_NAMENODE_OPTS}${HADOOP_DATANODE_OPTS}这两个变量,让JVM启动时带上libjvmti_perf.so这个插件,如下所示,修改完后重启运行perf的机器上的NameNode和DataNode。

1
2
export HADOOP_NAMENODE_OPTS="-Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:-INFO,RFAS} -Dhdfs.audit.logger=${HDFS_AUDIT_LOGGER:-INFO,NullAppender} -agentpath:/usr/libexec/perf-core/libs/libjvmti_perf.so -XX:+UseOprofile $HADOOP_NAMENODE_OPTS"
export HADOOP_DATANODE_OPTS="-Dhadoop.security.logger=ERROR,RFAS -agentpath:/usr/libexec/perf-core/libs/libjvmti_perf.so -XX:+UseOprofile $HADOOP_DATANODE_OPTS"

3. 画CPU火焰图

先把Brendan Gregg大神的FlameGraph脚本下载下来

1
2
git clone git@github.com:brendangregg/FlameGraph.git
cd FlameGraph

执行HDFS的TestDFSIO压测下,让DataNode有点动静

1
hadoop jar ${HADOOP_HOME}/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.4.0-tests.jar TestDFSIO -read -nrFiles 100 -size 1000MB -resFile ./logs/write.result

然后开启perf分析下DataNode,这里11691是DataNode的进程ID。

1
perf record -a -g -p 11691

等到TestDFSIO运行完成后,可以看到当前目录下面已经有perf.data这个文件,这时候用perf report --stdio已经可以直接查看结果。但是如果需要查看火焰图的话,需要再对perf.data做下转换。

1
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl >perf.svg

执行上面的命令后,把perf.svg搞到本地后,用浏览器打开就可以看到类似下面的结果。实际上生成的是svg,所以鼠标移上去左下角会显示完整的Java方法名。但由于下图只是截屏,所以没法体验到这效果
TestDFSIO火焰图

4. 画IO热力图

步骤很类似,把Brendan Gregg大神的HeatMap脚本下载下来

1
2
git clone git@github.com:brendangregg/HeatMap.git
cd HeatMap

执行HDFS的TestDFSIO压测下,让DataNode有点动静

1
hadoop jar ${HADOOP_HOME}/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.4.0-tests.jar TestDFSIO -read -nrFiles 100 -size 1000MB -resFile ./logs/write.result

然后开启perf分析下DataNode磁盘IO事件,这里block_rq_issu是发出IO请求时的事件,block_rq_complete是IO请求处理结束时的事件,11691是DataNode的进程ID,会统计DataNode发出IO请求到操作系统处理完IO请求的时间。

1
perf record -e block:block_rq_issue -e block:block_rq_complete -a

等到TestDFSIO运行完成后,可以看到当前目录下面已经有perf.data这个文件,执行如下命令生成IO热力图。

1
2
perf script | awk '{ gsub(/:/, "") } $5 ~ /issue/ { ts[$6, $10] = $4 } $5 ~ /complete/ { if (l = ts[$6, $9]) { printf "%.f %.f\n", $4 * 1000000, ($4 - l) * 1000000; ts[$6, $10] = 0 } }' > out.lat_us
./trace2heatmap.pl --unitstime=us --unitslat=us --stepsec=40 --maxlat=100000 out.lat_us > out.svg

执行上面的命令后,把out.svg搞到本地后,用浏览器打开就可以看到类似下面的结果。横坐标是时间轴,单位由上面的--stepsec参数指定,纵坐标是IO操作的延迟时间,单位由上面的--unitstime参数指定,上限由--maxlat指定,由于TestDFSIO指定运行时间较短,所以perf搜集到的IO操作并不是那么多,如果运行的时间更长的话,生成的热力图会更加壮观。生成的是svg,所以鼠标移上去左下角会显示每个点的具体含义。但由于下图只是截屏,所以没法体验到这效果。
TestDFSIO热力图

5. 结束

perf是个很强大的工具,Brendan Gregg大神的这几个脚本可以让我们更直观地查看perf的分析结果。不过怎么结合这些分析结果改进程序的性能,还是需要对程序的代码足够了解才能有的放矢地进行改进。

Hadoop lzo找不到Native库解决方法

Hadoop lzo相关的错误有两个,分别为:

  1. Could not load native gpl library
  2. native-lzo library not available
    下面会分别说明

Could not load native gpl library

很多HBase用户在用BulkLoad从Hadoop往HBase导入数据的时候,会遇到如下情况。报hadoop lzo找不到gplcompression的错误。

1
2
3
4
5
6
ERROR lzo.GPLNativeCodeLoader: Could not load native gpl library
java.lang.UnsatisfiedLinkError: no gplcompression in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1738)
at java.lang.Runtime.loadLibrary0(Runtime.java:823)
at java.lang.System.loadLibrary(System.java:1028)
at com.Hadoop.compression.lzo.GPLNativeCodeLoader.<clinit>

这个错误是因为生成HFile的时候开启了LZO压缩,开启LZO压缩可以有效的减少HFile大小(压缩比平均20%),有效减少distcp传输时间。但由于云梯1的java.library.path路径下并不包含gplcompression这个Native库,所以若生成HFile时开启LZO,则会报如上错误。解决方法很简单,将hadoop-lzo-0.4.20-mr1(MapReduce 1编译版本)或hadoop-lzo-0.4.20(MapReduce 2编译版本)下载的jar包加入到-libjars参数重新执行即可。

原因是hadoop-lzo的作者考虑到了上述情况,所以直接将gplcompression打包进了jar中。查看hadoop-lzo-0.4.20-mr1.jar可发现,gplcompression的Native库,都已经加入到jar包中的 native/Linux-amd64-64/lib下面

1
2
3
4
5
6
7
8
9
$: jar -tf hadoop-lzo-0.4.20-mr1.jar |grep native
native/
native/Linux-amd64-64/
native/Linux-amd64-64/lib/
native/Linux-amd64-64/lib/libgplcompression.a
native/Linux-amd64-64/lib/libgplcompression.la
native/Linux-amd64-64/lib/libgplcompression.so.0.0.0
native/Linux-amd64-64/lib/libgplcompression.so.0
native/Linux-amd64-64/lib/libgplcompression.so

hadoop-lzo的实现中会先将gplcompression的Native库从jar包中解压到临时地址,并load进该库。详细代码可参见作者托管在Github上的代码GPLNativeCodeLoader#unpackBinaries

1
2
3
4
5
// locate the binaries inside the jar
String fileName = System.mapLibraryName(LIBRARY_NAME);
String directory = getDirectoryLocation();
// use the current defining classloader to load the resource
InputStream is = GPLNativeCodeLoader.class.getResourceAsStream(directory + "/" + fileName);

native-lzo library not available

另一个与Hadoop lzo常见的错误是:

1
java.lang.RuntimeException: native-lzo library not available

这个错误是执行你的写HDFS程序的机器没有安装lzo-devel,程序在LD_LIBRARY_PATH下找不到liblzo2.so.2导致的,在该机器上执行如下命令安装即可。

1
yum install lzo lzo-devel

或者直接到已安装lzo的机器上将/usr/lib64/liblzo2.so.2下到本地,然后代码中手动load即可。

1
System.load(liblzo2.so.2的存放地址);