Apache Hadoop的核心原则就是转移计算比转移数据代价更低。这就是我们尽可能地将计算转移到存储数据之处的原因。因此,HDFS通常使用大量的本地读取,也就是说,读取数据的客户端和要读取的数据在同一节点。
起初,HDFS本地读取和远程读取使用同一种方式:客户端端通过TCP套接字连接DataNode
,并通过DataTransferProtocol
传输数据。这种方式很简单,但是有一些不好的地方。例如,DataNode
需要为每个读取数据块的客户端维持一个线程和TCP套接字。内核中TCP协议是有开销的,DataTransferProtocol
本身也有开销。因此这里有值得优化的地方。
本文将介绍HDFS的一项新优化:安全短路本地读取(secure short-circuit local reads),该实现的好处,以及它是如何为你的应用提速的。
HDFS-2246 和 Short-Circuit LocalReads
在HDFS-2246中,Andrew Purtell、Suresh Srinivas、Jitendra Nath Pandey和Benoy Antony等人添加了一项“短路本地读取”优化。
其核心思想如下:因为客户端和数据在同一个节点,所以没必要再牵涉DataNode
,客户端自身直接从本地磁盘读取 数据。这个性能优化被加入了CDH。CHD是Cloudera的Hadoop及相关项目的分发。这个优化是在CDH3u3中被加入的。
HDFS-2246实现的短路本地读取是一个很好的开始,但也带来了许多配置上的烦恼。系统管理员必须改变DataNode数据目录权限,以便允许客户端打开相关文件。还需要定义一个白名单,列出允许的用户。通常,这些用户被放在一个特殊的UNIX用户组里。
不幸的是,这类权限改变带来了安全漏洞。有权限的用户就可以直接浏览所有数据了,不仅是他们需要的数据。这有点像把用户提升成超级用户!对包括HBase用户在内的一些用户而言,这是可以接受的。然而,一般而言,这会带来很多问题。因此,尽管有些专注的管理员启用了短路本地读取,然而这并不是一个常见的选择。
HDFS-347:让短路本地读取安全
HDFS-2246的主要问题就是它将DataNode的所有数据路径直接开放给了客户端。其实,只需要开放客户端关心的几个数据文件。
好在UNIX提供了这样的机制,文件描述符。HDFS-347 使用该机制实现了安全的本地短路读取。客户端向DataNode请求数据时,DataNode打开块文件和元数据文件,将它们直接传给客户端,而不是将路径传给客户端。因为文件描述符是只读的,客户端不能修改接收到的文件。同时由于客户端自身无法访问块所在的目录,它也就不能访问其他不该访问的数据。
Windows 有类似的在进程间传递文件描述符的机制。虽然Cloudera目前还没有在Hadoop内添加该特性的支持,indows用户可以将dfs.cient.use.legacy.blockreader.local
设置为true,以便使用legacy block reader。
缓存文件描述符
HDFS客户端经常多次读取相同的块文件(尤其是HBase)。为了加快这种场景下的本地读取,HDFS-2246实现的短路本地读取中包含了一个块路径的缓存。该缓存允许客户端重新打开最近读取的块文件,而不需要再去询问DataNode该块文件的路径信息。
新的短路实现包含了一个FileInputStreamCache
,用于取代路径缓存。缓存文件描述符。这比缓存路径要好,因为客户端不用重新打开文件才能重新读取块。我们发现这一实现提高了性能。
缓存的大小可以通过 dfs.client.read.shortcircuit.stream.cache.size
调整,缓存超时时间通过 dfs.client.read.shortcircuit.streams.cache.expiry.ms
设定。设置缓存大小为0即可关闭缓存。大多数情况下,默认配置就可以了。如果你所处的情景是特殊的大规模工作集,文件描述符限制也很高,那么你可以试着提高参数值。
HDFS-347配置
HDFS-347引入的短路本地读取,使任何HDFS用户都可以使用短路本地读取,而不是局限于配置过的少量用户。也不需要修改Unix用户组来设定谁可以访问DataNodeN路径。然而,由于Java标准库并不包含支持文件描述符传递的库,所以该特性需要使用JNI组建。你需要安装 libhadoop.so
库才能使用。
HDFS-347也要求设置一NUX域套接字路径,可通过 dfs.domain.socket.path
设置。该路径必须是安全的,以便阻止无特权的进程进行中间人攻击。套接字路径的每个组成目录的都必须是root或启动DataNode的用户所有,人人可写或者组成员可写的路径无法使用。
如果你安装cloudera包,rpm或者deb,它会为你创建一个安全的UNIX域套接字路径。同时会将libhadoop.so
安装到正确路径下。
可以参考 上游的文档 获取配置短路本地读取的信息。
性能
那么,新的实现有多快?我使用了一个名为hio_bench
的程序获取到一些性能统计数据。hio_bench
的代码可由此取得: https://github.com/cmccabe/hiotest
测试运行在8核 intelXeon 2.13 CPU 、12块磁盘的服务器上。我使用CDH4.3.1,底层使用ext4文件系统。 下图每个值是运行三次的平均值。有误差线。
在所有测试中,HDFS-347实现是最快的,这大概要归功于FileInputStreamCache
.相反HDFS-2246实现会重复打开ext4 块文件多次,打开文件可是一个代价很高的操作。
在随机读取的场景下,相对顺序读取,短路实现有更明显的优势。部分原因是短路读取还没有实现预读。可以参考HDFS-4697的讨论。
结论
短路本地读取是一个很好的例子,展示了Hadoop将计算带向数据的模型如何使优化成为可能。这个例子同时也展示了,在处理了规模不断增长的挑战之后,Cloudera正挑战提高集群中的每个节点的性能。
如果你正使用CDH4.2 或以上版本,试下这个新实现吧!