综述

hdfs用户需要获取一个DistributedFileSystem实例对象来处理文件系统的一些任务。而DistributedFileSystem实例对象又需要使用DFSClient来完成用户提出的任务需求。而DFSClient则又通过ClientProtocol来连接Namenode,以及直接连接Datanode来读取block数据。这就是它们三者的协作关系。

数据完整性

dfs.bytes-per-checksum默认设置512byte,CRC-32是4byte校验位,额外开销小于1%。出错会抛出IOException。

距离

同节点 distacne(/d1/r1/n1, /d1/r1/n1) = 0
同机架 distacne(/d1/r1/n1, /d1/r1/n2) = 2
同数据中心 distacne(/d1/r1/n1, /d1/r2/n1) = 4
不同数据中心 distance(/d1/r1/n1, /d2/r1/n1) = 6

NameNode只响应Block位置,不响应具体数据。如果客户端本身就是DataNode,会读本地副本。

DistributedFileSystem的open()

  1. Client向NameNode9000端口getFileInfo 返回 HdfsFileStatus类信息
  2. Client向NN9000端口getBlockLocations 返回 一组BlockLocation[] 类信息,返回所有Block的Location信息,Location由近到远排序。HDFS中距离都是/d1/r1/n1这种配置出来的。

DistributedFileSystem.open() 返回 FSDataInputStream

FSDataInputStream的read()

  1. Client向DN的50010端口这个BP-235376452-192.168.124.13-1568193706425……..] .M…….”..$DFSClient_NONMAPREDUCE_990822073_248
  2. DN返回数据

FSDataInputStream通过校验和确认Block完整性,损坏会从其他Datanode读取,将损坏块通知给Namenode。

读取完当前block的数据后,关闭与当前的DataNode连接,并为读取下一个block寻找最佳的DataNode。
当读完列表的block后,且文件读取还没有结束,客户端会继续向Namenode获取下一批的block列表。
读取完一个block都会进行checksum验证,如果读取datanode时出现错误,客户端会通知Namenode,然后再从下一个拥有该block拷贝的datanode继续读。
在读取数据的过程中,如果客户端在与数据节点通信出现错误,则尝试连接包含此数据块的下一个数据节点。
失败的数据节点将被记录,以后不再连接。//TODO 深入探索
Client向NN的9000端口 getServerDefaults 返回 FsServerDefaults //TODO 作用是啥

packet为单位来做校验
HDFS Client通过FSDataInputStream流读取离客户端最近的DN上的block,getBlockLocations时候已经将block的副本按照离客户端的网络拓扑距离进行了排序,此过程是在NN端完成的。

ClientProtocol中与客户端读取文件相关的方法主要有两个:getBlockLocations()和reportBadBlocks()。
客户端调用getBlockLocations()方法获取HDFS文件指定范围内所有数据块的位置信息,每个数据块的位置信息指的是存储这个数据块副本的所有Datanode的信息,而且会按照与当前客户端的距离远近排序;
客户端来调用reportBadBlocks()方法向Namenode汇报错误的数据块信息。

DistributedFileSystem.create()
检查客户端权限以及确保文件不存在
NN:9000
getFileInfo..org.apache.hadoop.hdfs.protocol.ClientProtocol
create
addBlock
getServerDefaults
FSDataOutputStream.write()
DN:50010
发这个
BP-235376452-192.168.124.13-1568193706425……..] …=..
…..”..#DFSClient_NONMAPREDUCE_1525634651_1
再发具体数据

NN:9000
complete
getListing

//TODO链式复制
所有Datanode构成一个Pipeline
dfs.namenode.replication.min默认为1,只要有这些个写入就表示成功。此Block会继续异步复制。

写入原则

跨机柜传输慢,故会在可靠性和性能间做权衡。一般情况下:机柜1一个DataNode,机柜2两个DataNode。
随机选择DataNode1。在和DataNode1不同机架上随机选择一台作为Datanode2。如果1和2在同个机架,那么就尝试在另外一个机架上选择DataNode)。而如果1和2没有在同一个机架上,则在DataNode2所在的机架上随机DataNode3。
得到3个DataNode的列表以后,从NameNode返回该列表到Client之前,会根据“距离”由近到远进行一个排序,客户端根据这个顺序有近到远的进行数据块的写入。
当客户端本地缓存一个Block大小数据后。上传最近DataNode,其他DataNode获取数据采用“流水式复制”
Client -> DN1 -> DN2 -> DN3 每批4K

写/追加写数据相关的方法

在HDFS客户端操作中最重要的一部分就是写入一个新的HDFS文件,或者打开一个已有的HDFS文件执行追加写操作。ClientProtocol中定义了8种方法支持HDFS写操作:

  • create() 方法用于在HDFS的文件系统目录树种创建一个新的空文件。然后再调用addBlock()方法获取存储文件数据的数据块的位置信息,最后客户端根据位置信息建立数据流管道写入数据。
  • append()方法用于打开一个已有的文件,如果这个文件的最后一个数据块没有写满,,则返回这个数据块的位置信息,如果写满,则新建一个数据块返回信息。
  • addBlock()客户端调用addBlock()方法向指定文件添加一个新的数据块,并获取数据块副本的所有数据节点的位置信息。
  • complete()当客户端完成了整个文件的写入操作后,会调用complete()方法通知Namnode。
    以上是正常情况,如遇到错误时,会有以下方法:
  • abandonBlock()当客户端获取了一个新的申请的数据块,发现无法建立到存储这个数据块副本的某些数据节点单位连接时,会调用这个方法通知名字节点放弃这个数据块,之后客户端再次调用addBlock()方法获取新的数据块,并在参数中将无法链接的节点传给名字节点,避免再次分到这个节点上。
  • getAdditionalDatanode()和updatePipeline()如果客户端已经成功建立了数据管道,在客户端写某个数据块,存储这个数据块副本的某个数据节点出现错误时,客户端首先会调用getAdditionalDatanode()方法向Namenode申请一个新的Datanode来替代出现故障的Datanode。然后客户端会调用updateBlockForPipepline()方法向Namenode申请为这个数据块分配新的时间戳,这样故障节点上的没能写完整的数据块的时间戳就会过期,在后续的块汇报操作中会被删除,最后客户端就可以使用新的时间戳建立新的数据管道,来执行对数据块的写操作。数据流管道建立成功后,客户端还需要调用updatePipeline()方法更新Namenode中当前数据块的数据管道信息。
  • renewLease() 当写的过程中clientfazh发生异常时,对于任意一个Client打开的文件都需要Client定期调用该方法更新租约,如果Namenodechangshiji长时间没有收到Client的租约更新消息,就会认为Client发生故障,这是就会触发一次租约恢复操作,关闭文件并且同步所有数据节点上这个文件数据块的状态,确保HDFS系统中这个文件是正确且一致保存的。
    如果写的时候Namenode节点出错,就涉及HDFS的HA。

副本存储放策略

第一副本:放置在上传文件的DataNode上;如果是集群外提交,则随机挑选一台磁盘不太慢、CPU不太忙的节点上;
第二副本:放置在于第一个副本不同的机架的节点上;
第三副本:与第二个副本相同机架的不同节点上;
如果还有更多的副本:随机放在节点中;

参考资料


http://shiyanjun.cn/archives/942.html