HDFS的几个典型的流程:客户端读HDFS文件流程、客户端写HDFS文件流程、客户端追加写HDFS文件流程、数据节点与名字节点交互流程以及HDFS HA切换流程等。node
1、客户端读HDFS文件流程
- 打开HDFS文件:HDFS客户端首先调用DistributedFileSystem.open()方法打开HDFS文件,这个方法在底层会调用ClientProtocol.open()方法,该方法会返回一个HdfsDataInputStream对象用于读取数据块。HdfsDataInputStream实际上是一个DFSInputStream的装饰类,真正进行数据块读取操做的是DFSInputStream对象。
- 从Namenode获取Datanode地址:在DFSInputStream的构造方法中,会调用ClientProtocol.getBlockLocations()方法向名字节点获取该HDFS文件起始位置数据块的位置信息。Namenode返回的数据块的存储位置是按照与客户端的距离远近排序的,因此DFSInputStream能够选择一个最优的Datanode节点,而后与这个节点创建数据链接读取数据块。
- 链接到Datanode读取数据块:HDFS客户端经过调用DFSInputStream.read()方法从这个最优的Datanode读取数据块,数据会以数据包(packet)为单位从数据节点经过流式接口传送到客户端。当达到一个数据块的末尾时,DFSInputStream就会再次调用ClientProtocol.getBlockLocations()获取文件下一个数据块的位置信息,并创建和这个新的数据块的最优节点之间的链接,而后HDFS客户端就能够继续读取数据块了。
- 关闭输入流:当客户端成功完成文件读取后,会经过HdfsDataInputStream.close()方法关闭输入流。
流程图以下:缓存

客户端读取数据块时,颇有可能存储这个数据块的数据节点出现异常,也就是没法读取数据。出现这种状况时,DFSInputStream会切换到另外一个保存了这个数据块副本的数据节点而后读取数据。同时须要注意的是,数据块的应答包中不只包含了数据,还包含了校验值。HDFS客户端接收到数据应答包时,会对数据进行校验,若是出现校验错误,也就是数据节点上的这个数据块副本出现了损坏,HDFS客户端就会经过ClientProtocol.reportBadBlocks()向Namenode汇报这个损坏的数据块副本,同时DFSInputStream会尝试从其余的数据节点读取这个数据块。spa
2、客户端写HDFS文件流程
- 建立文件: HDFS客户端写-一个 新的文件时,会首先调用DistributedFileSystem.create()方法在HDFS文件系统中建立一个新的空文件。这个方法在底层会经过调用ClientProtocol.create()方法通知Namenode执行对应的操做, Namenode会首先在文件系统目录树中的指定路径下添加一个新的文件,而后将建立新文件的操做记录到editlog中。完成ClientProtocol.create()调用后, DistributedFileSystem.create()方法就会返回一个HdfsDataOutputStream对象,这个对象在底层包装了一个DFSOutputStream对象,真正执行写数据操做的实际上是DFSOutputStream对象。
- 创建数据流管道:获取了DFSOutputStream对象后,HDFS客户端就能够调用 DFSOutputStream.write()方法来写数据了。因为DistributedFileSystem.create()方法只是在文件系统目录树中建立了一个空文件,并无申请任何数据块,因此DFSOutputStream会首先调用ClientProtocol.addBlock()向Namenode申请一个新的空数据块,addBlock()方法会返回一个LocatedBlock对象,这个对象保存了存储这个数据块的全部数据节点的位置信息。得到了数据流管道中全部数据节点的信息后,DFSOutputStream就能够创建数据流管道写数据块了。
- 经过数据流管道写入数据: 成功地创建数据流管道后,HDFS客户端就能够向数据流管道写数据了。写入DFSOutputStream中的数据会先被缓存在数据流中,以后这些数据会被切分红一个个数据包(packet)经过数据流管道发送到全部数据节点。经过数据流管道依次写入数据节点的本地存储。每一个数据包都有个确认包(ack),确认包会逆序经过数据流管道回到输出流。输出流在确认了全部数据节点已经写入这个数据包以后,就会从对应的缓存队列删除这个数据包。当客户端写满一个数据块以后,会调用addBlock()申请一个新的数据块,而后循环执行上述操做。
- 关闭输入流并提交文件: 当HDFS客户端完成了整个文件中全部数据块的写操做以后,就能够调用close()方法关闭输出流,并调用ClientProtocol.complete()方法通知Namenode提交这个文件中的全部数据块,也就完成了整个文件的写入流程。
流程图以下:
对象
对于Datanode,当 Datanode成功地接受一个新的数据块时,Datanode会经过
DatanodeProtocol.blockReceivedAndDeleted()方法向Namenode汇报,Namenode会更新内存中的数据块与数据节点的对应关系。blog
若是客户端在写文件时,数据流管道中的数据节点出现故障,则输出流会进行以下操做来进行故障恢复。排序
- 输出流中缓存的没有确认的数据包会从新加入发送队列,这种机制确保了数据节点出现故障时不会丢失任何数据,全部的数据都是通过确认的。输出流会经过调用ClientProtocol.updateBlockForPipeline()方法为数据块申请一个新的时间戳,而后使用这个新的时间戳从新创建数据流管道。这种机制保证了故障Datanode上的数据块的时间戳会过时,而后在故障恢复以后,因为数据块的时间戳与Namenode元数据中的不匹配而被删除,保证了集群中全部数据块的正确性。
- 故障数据节点会从输入流管道中删除,而后输出流会经过调用ClientProtocol.getAdditionalDatanode()方法通知Namenode分配新的数据节点到数据流管道中。接下来输出流会将新分配的Datanode添加到数据流管道中,并使用新的时间戳从新创建数据流管道。因为新添加的数据节点上并无存储这个新的数据块,这时HDFS客户端会经过DataTransferProtocol通知数据流管道中的一个Datanode复制这个数据块到新的Datanode上.
- 数据流管道从新创建以后,输出流会调用ClientProtocol.updatePipeline()更新Namenode中的元数据。至此,一个完整的故障恢复流程就完成了,客户端能够正常完成后续的写操做了。
3、Datanode启动、心跳以及执行名字节点指令流程
Datanode启动后与 Namenode 的交互主要包括三个部分:①握手;②注册;③块汇报以 及缓存汇报。接口
- Datanode启动时会首先经过DatanodeProtocol.versionRequest()获取Namenode的版本号以及存储信息等,而后Datanode会对Namenode的当前软件版本号和Datanode的当前软件、版本号进行比较,确保它们是一致的。
- 成功地完成握手操做后,Datanode会经过DatanodeProtocol.register()方法向Namenode注册。Namenode接收到注册请求后,会判断当前Datanode的配置是否属于这个集群,它们之间的版本号是否一致。
- 注册成功以后,Datanode就须要将本地存储的全部数据块以及缓存的数据块上报到Namenode,Namenode会利用这些信息从新创建内存中数据块与Datanode之间的对应关系。
至此,Datanode 就完成了启动的全部操做,以后就能够正常对外服务了。队列
Datanode成功启动以后,须要按期向Namenode发送心跳,让Namenode知道当前Datanode 处于活动状态可以对外服务。Namenode 会在Datanode 的心跳响应中携带名字节点指令,指导Datanode进行数据块的复制、删除以及恢复等操做。图片
当Datanode成功地添加了一个新的数据块或者删除了-一个已有的数据块时,须要经过 DatanodeProtocol.blockReceivedAndDeleted(方法向Namenode汇报。Namenode接收到这个汇 报以后,会更新Namenode内存中数据块与数据节点之间的对应关系。ip