一、写入数据
HDFS写入数据时,根据block存储策略可能不同。首先要看是否启用了hadoop的机架感知,默认是关闭的,所有机架名为“/default-rack”所以在hdfs写入数据时是随机的,也就是说,很可能hadoop将第一个数据块block1写到rack1的node1上,然后将block2写在了rack2上node2上,再接下来随机的选择block3又重新写回了rack1上的node3上时,这样rack间的数据流量会成倍增加,成为性能的瓶颈,进而影响整个集群的服务。启用机架感知,只需修改在namenode的配置文件hadoop-site.xml中的选项:
<property>
<name>topology.script.file.name</name>
<value>/path/to/RackAware.py</value>
</property>
这个配置选项的value指定为一个可执行程序,通常为一个脚本,该脚本接受一个参数,输出一个值。接受的参数通常为某台datanode机器的 ip地址,而输出的值通常为该ip地址对应的datanode所在的rack,例如"/rack1"。Namenode启动时,会判断该配置选项是否为空,如果非空,则表示已经用机架感知的配置,此时namenode会根据配置寻找该脚本,并在接收到每一个datanode的heartbeat时,将该 datanode的ip地址作为参数传给该脚本运行,并将得到的输出作为该datanode所属的机架,保存到内存的一个map中。
至于脚本的编写,就需要将真实的网络拓朴和机架信息了解清楚后,通过该脚本能够将机器的ip地址正确的映射到相应的机架上去。一个简单的实现如下:
!/usr/bin/python
--coding:UTF-8 --
import sys
rack = {"hostname1":"rack1",
"hostname2":"rack1",
"hostname3":"rack2",
"hostname4":"rack2",
"ip1":"rack1",
"ip2":"rack1",
"ip3":"rack2",
"ip4":"rack2",
}
if name=="main":
print "/" + rack.get(sys.argv[1],"rack0")
而当Hadoop集群中配置了机架感知信息以后,hadoop在选择三个datanode时,就会进行相应的判断:
1.如果上传本机不是一个datanode,而是一个客户端,那么就从接近客户端所在的机架下slave机器中随机选择一台datanode作为第一个块block1的写入机器(datanode1)。
注意:而此时如果上传机器本身就是一个datanode(例如mapreduce作业中task通过DFSClient向hdfs写入数据的时候),那么就将该datanode本身作为第一个块写入机器(datanode1)。
2.随后在datanode1所属的机架以外的另外的机架上,随机的选择一台,作为第二个block的写入datanode机器(datanode2)。
3.在写第三个block前,先判断是否前两个datanode是否是在同一个机架上,如果是在同一个机架,那么就尝试在另外一个机架上选择第
三个datanode作为写入机器(datanode3)。而如果datanode1和datanode2没有在同一个机架上,则在datanode2所在的机架上选择一台datanode作为datanode3。
4.得到3个datanode的列表以后,从namenode返回该列表到DFSClient之前,会在namenode端首先根据该写入客户端跟 datanode列表中每个datanode之间的"距离"由近到远进行一个排序。如果此时DFS写入端不是datanode,则选择datanode列表中的第一个排在第一位。客户端根据这个顺序有近到远的进行数据块的写入。
因此,判断两个datanode之间"距离"的算法就比较关键,hadoop目前实现如下,以两个表示datanode的对象DatanodeInfo(node1,node2)为例:
每个datanode都会对应自己在集群中的位置和层次,如node1的位置信息为"/rack1/datanode1",那么它所处的层次就为2,其余类推。得到两个node的层次后,会沿着每个node所处的拓朴树中的位置向上查找,如"/rack1/datanode1"的上一级就是" /rack1",此时两个节点之间的距离加1,两个node分别同上向上查找,直到找到共同的祖先节点位置,此时所得的距离数就用来代表两个节点之间的距
离。所以,如上图所示,node1和node2之间的距离就为4.
5.当根据"距离"排好序的datanode节点列表返回给DFSClient以后,DFSClient便会创建BlockOutputStream,并将这次block写入pipeline中的第一个节点(最近的节点)。写入块时,以更小的数据包packet(64k)写入,datanode写入一个小单位完成后就会把这个小单位的数据推送给下一个datanode,直到第一个block的最后一个数据包在第三个备份完成,向namenode报告写完了第一个block。第一个datanode向client 通知写完了,然后client向namenode确认写完以后,第一个block写入完成。其中校验和写入时并行的。
6.写完第一个block以后,依次按照datanode列表中的次远的node进行写入,直到最后一个block写入成功,DFSClient返回成功,该block写入操作结束。
概括下:client负责切割文件,NN负责为数据块分配DN,DN对数据进行存储冗余!!
数据块的第一个副本优先放在写入数据块的客户端所在的节点上,但是如果这个客户端上的数据节点空间不足或者是当前负载过重,则应该从该数据节点所在的机架中选择一个合适的数据节点作为本地节点。如果客户端上没有一个数据节点的话,则从整个集群中随机选择一个合适的数据节点作为此时这个数据块的本地节点。HDFS的存放策略是将一个副本存放在本地机架节点上,另外两个副本放在不同机架的不同节点上。这样集群可在完全失去某一机架的情况下还能存活。同时,这种策略减少了机架间的数据传输,提高了写操作的效率,因为数据块只存放在两个不同的机架上,减少了读取数据时需要的网络传输总带宽。这样在一定程度上兼顾了数据安全和网络传输的开销。
通过以上策略,namenode在选择数据块的写入datanode列表时,就充分考虑到了将block副本分散在不同机架下,并同时尽量的避免了之前描述的过多的网络开销。
读取数据
我们看一下Hadoop集群配置中如何读取数据。当对某个文件的某个block进行读取的时候,hadoop采取的策略也是一样:
1.首先得到这个block所在的datanode的列表,有几个副本数该列表就有几个datanode。用a列列出文件的数据块,b列列出数据块的对应的DN,告诉client,client知道用多少数据块可以下载,并知道数据块的位置。
2.根据列表中datanode距离读取端的距离进行从小到大的排序:
a)首先查找本地是否存在该block的副本,如果存在,则将本地datanode作为第一个读取该block的datanode
b)然后查找本地的同一个rack下是否有保存了该block副本的datanode
c)最后如果都没有找到,或者读取数据的node本身不是datanode节点,则返回datanode列表的一个随机顺序。