一、HDFS的写入流程
1.1、文件上传流程如下:
创建文件:
- HDFS
client
向HDFS写入数据,先调用DistributedFileSystem. create()
RPC
调用namenode
的create()
,会在HDFS目录树中指定的路径,添加新文件,并将操作记录在edits.log
中。namenode.create()
方法执行完后,返回一个FSDataOutputStream
,它是DFSOutputStream
的包装类。
建立数据流管道pipeline:
client
调用DFSOutputStream.write()
写数据 (先写第一个块的数据, 暂时叫blk1
)DFSOutputStream
通过RPC
调用namenode
的addBlock
,向namenode
申请一 个空的数据块block
addBlock
返回LocatedBlock
对象;此对象中包含了当前blk
要存储在哪三个datanode
的信息,比如dn1、dn2、dn3
- 客户端,根据位置信息,建立数据流管道(图中蓝色线条)
向数据流管道写当前块的数据:
- 写数据时,先将数据写入一个检验块
chunk
中,写满512
字节后,对此chunk
计算校验和checksum
值(4字节) - 然后将
chunk
及对应校验和写入packet
中,一个packet
是64KB
- 随着源源不断的带校验和的
chunk
写入packet
,当packet
写满后,将packet
写 入dataqueue
数据队列中 packet
从队列中取出,沿pipeline
发送到dn1
,再从dn1
发送到dn2
,再从dn2
发送到dn3
- 同时,此
packet
会保存一份到一 个确认队列ack queue
中 packet
到达最后一个datanode
即dn3
后,做校验,将校验结果逆着pipeline
方向回传到客户端,具体是校验结果从dn3
传到dn2
,dn2
也会做校验,校验结果再传到dn1
,dn1
也做校验,结果再传回客户端- 客户端根据校验结果,如果“成功”,则将保存在
ack queue
中的packet
删除;如果失败,则将packet
取出,重新放回到data queue
末尾,等待再次沿pipeline
发送 - 如此,将
block
中的一个数据一个个packet
发送出去;当此block
发送完毕,即dn1、dn2、dn3
都接受了blk1
的完整的副本,那么三个dn
分别RPC
调用namenode
的blockReceivedAndDeleted()
,namenode
会更新内存中block
与datanode
的对应关系(比如dn1
上多了一个blk1
副本)
关闭dn1. dn2. dn3
构建的pipeline
,文件还有下一个块时,再从4
开始,直到文件全部数据写完:
-
最终,调用
DFSOutputStream的close0
-
客户端调用
namenode
的complete()
,告知namende
文件传输完成
1.2、容错机制
问题描述:
假设说当前构建的pipeline
是dn1、dn2、 dn3
构成的,当传输数据的过程中,dn2
挂了或通信不畅,则当前pipeline
中断,HDFS会如何做?
解决:
-
先将
ack queue
中的所有packet
全部放回到data queue
中 -
客户端
RPC
调用namenode
的updateBlockForPipeline()
,为当前block
(假设是blk1
)生成新的版本比如ts1
(本质是时间戳) -
故障
dn2
会从pipeline
中删除 -
DFSOutputStream
再RPC
调用namenode
的getAdditionalDatanode()
,让namenode
分配新的datanode
,比如是dn4
-
输出流将原
dn1、dn3
与新的dn4
组成新的管道,他们上边的blk1
版本设置为新版本ts1
-
由于新添加的
dn4
上没有blk1
的数据,客户端告知dn1
或dn3
,将其上的blk1
的数据拷贝到dn4
上 -
新的数据管道建立好后,
DFSOutputStream
调用updatePipeline()
更新namenode
元数据 -
至此,
pipeline
恢复, 客户端按正常的写入流程,完成文件的上传 -
故障
datanode
重启后,namenode
发现它上边的block
的blk1
的时间戳是老的,会让datanode
将blk1
删除掉
参考文献:HDFS容错机制详解
二、HDFS的读取流程
文件读取流程如下:
-
客户端通过
DistributedFileSystem
向NameNode
请求下载文件,NameNode
通过查询元数据,找到文件块所在的DataNode
地址。 -
挑选一台
DataNode
(就近原则,然后随机)服务器,请求读取数据。 -
DataNode
开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet
为单位来做校验)。 -
客户端以
Packet
为单位接收,先在本地缓存,然后写入目标文件。