hdfs优化

HDFS作为HBase最终数据存储系统,通常会使用三副本策略存储HBase数据文件以及日志文件。从HDFS的角度望上层看,HBase即是它的客户端,HBase通过调用它的客户端进行数据读写操作,因此HDFS的相关优化也会影响HBase的读写性能。

Short-Circuit Local Read功能是否开启?

优化原理:当前HDFS读取数据都需要经过DataNode,客户端会向DataNode发送读取数据的请求,DataNode接受到请求之后从硬盘中将文件读出来,再通过TPC发送给客户端。Short Circuit策略允许客户端绕过DataNode直接读取本地数据。(具体原理参考此处 http://blog.cloudera.com/blog/2013/08/how-improved-short-circuit-local-reads-bring-better-performance-and-security-to-hadoop/ )

优化建议:开启Short Circuit Local Read功能,具体配置戳这里 (https://hadoop.apache.org/docs/r2.7.2/hadoop-project-dist/hadoop-hdfs/ShortCircuitLocalReads.html )

详解HDFS Short Circuit Local Reads

Hadoop的一大基本原则是移动计算的开销要比移动数据的开销小。因此,Hadoop通常是尽量移动计算到拥有数据的节点上。这就使得Hadoop中读取数据的客户端DFSClient和提供数据的Datanode经常是在一个节点上,也就造成了很多“Local Reads”。

最初设计的时候,这种Local Reads和Remote Reads(DFSClient和Datanode不在同一个节点)的处理方式都是一样的,也就是都是先由Datanode读取数据,然后再通过RPC把数据传给DFSClient。这样处理是比较简单的,但是性能会受到一些影响,因为需要Datanode在中间做一次中转。本文将介绍针对这个问题的一些优化。

既然DFSClient和数据是在一个机器上面,那么很自然的想法,就是让DFSClient绕开Datanode自己去读取数据,在具体实现上有如下两种方案。

HDFS-2246

在这个JIRA中,工程师们的想法是既然读取数据DFSClient和数据在同一台机器上,那么Datanode就把数据在文件系统中的路径,从什么地方开始读(offset)和需要读取多少(length)等信息告诉DFSClient,然后DFSClient去打开文件自己读取。想法很好,问题在于配置复杂以及安全问题。

首先是配置问题,因为是让DFSClient自己打开文件读取数据,那么就需要配置一个白名单,定义哪些用户拥有访问Datanode的数据目录权限。如果有新用户加入,那么就得修改白名单。需要注意的是,这里是允许客户端访问Datanode的数据目录,也就意味着,任何用户拥有了这个权限,就可以访问目录下其他数据,从而导致了安全漏洞。因此,这个实现已经不建议使用了。

HDFS-347

在Linux中,有个技术叫做Unix Domain Socket。Unix Domain Socket是一种进程间的通讯方式,它使得同一个机器上的两个进程能以Socket的方式通讯。它带来的另一大好处是,利用它两个进程除了可以传递普通数据外,还可以在进程间传递文件描述符。

假设机器上的两个用户A和B,A拥有访问某个文件的权限而B没有,而B又需要访问这个文件。借助Unix Domain Socket,可以让A打开文件得到一个文件描述符,然后把文件描述符传递给B,B就能读取文件里面的内容了即使它没有相应的权限。在HDFS的场景里面,A就是Datanode,B就是DFSClient,需要读取的文件就是Datanode数据目录中的某个文件。

这个方案在安全上就比上一个方案上好一些,至少它只允许DFSClient读取它需要的文件。

如果你想了解更多关于Unix Domain Socket的知识,可以看看:http://www.thomasstover.com/uds.htmlhttp://troydhanson.github.io/misc/Unix_domain_sockets.html
如何配置
因为Java不能直接操作Unix Domain Socket,所以需要安装Hadoop的native包libhadoop.so。如果你的集群是用各大Hadoop发行版(比如Pivotal HD,CDH等)来安装的,这些native包通常在安装Hadoop的时候会被安装好的。你可以用如下命令来检查这些native包是否安装好。
CDH版本,native包check已安装:

1
2
3
4
5
6
7
8
9
[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ /home/q/hadoop/q_hadoop/bin/hadoop checknative

Native library checking:
hadoop: true /home/q/hadoop/hadoop-2.5.0-cdh5.2.0/lib/native/libhadoop.so
zlib: true /lib64/libz.so.1
snappy: true /home/q/hadoop/hadoop-2.5.0-cdh5.2.0/lib/native/libsnappy.so.1
lz4: true revision:99
bzip2: false
openssl: false Cannot load libcrypto.so.1.0.0 (libcrypto.so.1.0.0: cannot open shared object file: No such file or directory)!

Short Circuit Local Reads相关的配置项(在hdfs-site.xml中)如下:

1
2
3
4
5
6
7
8
<property>
<name>dfs.client.read.shortcircuit</name>
<value>true</value>
</property>
<property>
<name>dfs.domain.socket.path</name>
<value>/home/q/hadoop/hadoop-2.5.0-cdh5.2.0/etc/hadoop/dn_socket</value>
</property>

其中:dfs.client.read.shortcircuit是打开这个功能的开关,dfs.domain.socket.path是Datanode和DFSClient之间沟通的Socket的本地路径。

如何确认配置生效了

按照上面的配置,如何确认从HDFS读取数据的时候,Short Circuit Local Reads真的起作用了?通常生成了socket文件即可表示成功,细粒度查看可以通过如下方法:

查看dn_socket文件是否真实存在和Datanode的日志.

1
2
[hadoop@hostname /home/q/hadoop/q_hadoop/etc/hadoop]$ ls /home/q/hadoop/q_hadoop/etc/hadoop/dn_socket
/home/q/hadoop/q_hadoop/etc/hadoop/dn_socket

在Datanode的启动日志中,也可以看到如下相关的日志表明Unix Domain Socket被启用了。

1
2
3
4
5
2017-06-14 17:28:09,098 INFO org.apache.hadoop.hdfs.server.datanode.DataNode: Number threads for balancing is 5
2017-06-14 17:28:09,101 INFO org.apache.hadoop.hdfs.server.datanode.DataNode: Balancing bandwith is 5242880 bytes/s
2017-06-14 17:28:09,101 INFO org.apache.hadoop.hdfs.server.datanode.DataNode: Number threads for balancing is 5
2017-06-14 17:28:09,101 INFO org.apache.hadoop.hdfs.server.datanode.DataNode: Listening on UNIX domain socket:
/home/q/hadoop/hadoop-2.5.0-cdh5.2.0/etc/hadoop/dn_socket

往hdfs创建一个tmp目录并上传一个a.txt文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ ./hdfs dfs -mkdir /tmp  

[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ ./hdfs dfs -ls /
Found 2 items
drwxr-xr-x - hadoop supergroup 0 2017-03-06 11:10 /hbase
drwxr-xr-x - hadoop supergroup 0 2017-06-14 17:45 /tmp


[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ touch /tmp/a.txt
[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ echo 'Hello HBase!' > /tmp/a.txt

[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ ./hdfs dfs -put /tmp/a.txt /tmp
[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ ./hdfs dfs -ls /tmp
Found 1 items
-rw-r--r-- 3 hadoop supergroup 13 2017-06-14 17:58 /tmp/a.txt


[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ ./hdfs fsck /tmp/a.txt -files -blocks
17/06/14 17:59:25 WARN ssl.FileBasedKeyStoresFactory: The property 'ssl.client.truststore.location' has not been set,
no TrustStore will be loaded
Connecting to namenode via http://hostname:50070
FSCK started by hadoop (auth:SIMPLE) from /10.90.18.144 for path /tmp/a.txt at Wed Jun 14 17:59:25 CST 2017
/tmp/a.txt 13 bytes, 1 block(s): OK
0. BP-1703971618-10.90.18.140-1488769422929:blk_1073784567_43767 len=13 repl=3

Status: HEALTHY
Total size: 13 B
Total dirs: 0
Total files: 1
Total symlinks: 0
Total blocks (validated): 1 (avg. block size 13 B)
Minimally replicated blocks: 1 (100.0 %)
Over-replicated blocks: 0 (0.0 %)
Under-replicated blocks: 0 (0.0 %)
Mis-replicated blocks: 0 (0.0 %)
Default replication factor: 3
Average block replication: 3.0
Corrupt blocks: 0
Missing replicas: 0 (0.0 %)
Number of data-nodes: 5
Number of racks: 1
FSCK ended at Wed Jun 14 17:59:25 CST 2017 in 1 milliseconds

The filesystem under path '/tmp/a.txt' is HEALTHY
BP-1703971618-10.90.18.140-1488769422929:blk_1073784567_43767 len=13 repl=3 可以看出该文件有一个block,id是:blk_1073784567_43767

将文件拷贝到本地:

1
2
[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ rm -rf /tmp/a.txt
[hadoop@hostname /home/q/hadoop/q_hadoop/bin]$ ./hadoop fs -get /tmp/a.txt /tmp

datanode日志中会打印读取block使用了Short Circuit Local Reads读取block。 op: REQUEST_SHORT_CIRCUIT_FDS

1
2
3
4
5
6
2017-06-14 18:03:10,513 INFO org.apache.hadoop.hdfs.server.datanode.DataNode.clienttrace: 
cliID: DFSClient_NONMAPREDUCE_859197200_1, src: 127.0.0.1, dest: 127.0.0.1, op: REQUEST_SHORT_CIRCUIT_SHM,
shmId: 3acf48858a1aca1c047bccc214f8ea8d, srvID: 8af8b978-b80f-4c5b-9ea4-8cd1d8d91200, success: true
2017-06-14 18:03:10,536 INFO org.apache.hadoop.hdfs.server.datanode.DataNode.clienttrace: src: 127.0.0.1,
dest: 127.0.0.1, op: REQUEST_SHORT_CIRCUIT_FDS, blockid: 1073784567, srvID: 8af8b978-b80f-4c5b-9ea4-8cd1d8d91200,
success: true

Hedged Read功能是否开启?

优化原理:HBase数据在HDFS中一般都会存储三份,而且优先会通过Short-Circuit Local Read功能尝试本地读。但是在某些特殊情况下,有可能会出现因为磁盘问题或者网络问题引起的短时间本地读取失败,为了应对这类问题,社区开发者提出了补偿重试机制 – Hedged Read。该机制基本工作原理为:客户端发起一个本地读,一旦一段时间之后还没有返回,客户端将会向其他DataNode发送相同数据的请求。哪一个请求先返回,另一个就会被丢弃。

1
2
3
This feature is off by default. To enable this feature, set <code>dfs.client.hedged.read.threadpool.size</code> to a positive number. The threadpool size is how many threads to dedicate to the running of these 'hedged', concurrent reads in your client.

Then set <code>dfs.client.hedged.read.threshold.millis</code> to the number of milliseconds to wait before starting up a 'hedged' read. For example, if you set this property to 10, then if a read has not returned within 10 milliseconds, we will start up a new read against a different block replica.

优化建议:开启Hedged Read功能,具体配置参考这里 (https://issues.apache.org/jira/browse/HDFS-5776)

配置步骤

  1. 修改一台rs hbase-site.xml,分发到其它rs并重启整个集群。
    1
    2
    3
    4
    5
    6
    7
    8
    <property>
    <name>dfs.client.hedged.read.threadpool.size</name>
    <value>20</value> <!-- 默认20 threads -->
    </property>
    <property>
    <name>dfs.client.hedged.read.threshold.millis</name>
    <value>50</value> <!-- 默认10 milliseconds -->
    </property>
1
for i in `cat regionservers`;do "scp hbase-site.xml $i:/home/q/hbase/q_hbase/conf/ ";done
  1. 重启整个集群RS
    1
    /home/q/hbase/q_hbase/bin/hbase-daemons.sh restart regionserver
1
2
3
4
5
his feature emits new metrics: 

+ hedgedReadOps
+ hedgeReadOpsWin -- how many times the hedged read 'beat' the original read
+ hedgedReadOpsInCurThread -- how many times we went to do a hedged read but we had to run it in the current thread because dfs.client.hedged.read.threadpool.size was at a maximum.