HDFS 的几个典型的流程:客户端读 HDFS 文件流程、客户端写 HDFS 文件流程、客户端追加写 HDFS 文件流程、数据节点与名字节点交互流程以及 HDFS HA 切换流程等。
一、客户端读 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 会尝试从其余的数据节点读取这个数据块。
二、客户端写 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 会更新内存中的数据块与数据节点的对应关系。
如果客户端在写文件时,数据流管道中的数据节点呈现故障,则输入流会进行如下操作来进行故障复原。
- 输入流中缓存的没有确认的数据包会重新加入发送队列,这种机制确保了数据节点呈现故障时不会失落任何数据,所有的数据都是通过确认的。输入流会通过调用 ClientProtocol.updateBlockForPipeline()办法为数据块申请一个新的工夫戳,而后应用这个新的工夫戳从新建设数据流管道。这种机制保障了故障 Datanode 上的数据块的工夫戳会过期,而后在故障复原之后,因为数据块的工夫戳与 Namenode 元数据中的不匹配而被删除,保障了集群中所有数据块的正确性。
- 故障数据节点会从输出流管道中删除,而后输入流会通过调用 ClientProtocol.getAdditionalDatanode()办法告诉 Namenode 调配新的数据节点到数据流管道中。接下来输入流会将新调配的 Datanode 增加到数据流管道中,并应用新的工夫戳从新建设数据流管道。因为新增加的数据节点上并没有存储这个新的数据块,这时 HDFS 客户端会通过 DataTransferProtocol 告诉数据流管道中的一个 Datanode 复制这个数据块到新的 Datanode 上.
- 数据流管道从新建设之后,输入流会调用 ClientProtocol.updatePipeline()更新 Namenode 中的元数据。至此,一个残缺的故障复原流程就实现了,客户端能够失常实现后续的写操作了。
三、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 内存中数据块与数据节点之间的对应关系。