08. 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对象,这个对象保存了存储这个数据块的所有Datanode的位置信息。获得了数据流管道中所有的Datanode信息后,DFSOutputStream就可以建立数据流管道写数据块了。
-
通过数据流管道写入数据
成功地建立数据流管道后,HDFS客户端就可以向数据流管道写数据了。写入DFSOutputStream中的数据会先被缓存在数据流中,之后这些数据会被切分成一个个数据包(packet)通过数据流管道发送到所有Datanode。这里的每个数据包都会像上图所示,通过数据流管道依次写入Datanode的本地存储。每个数据包都有个确认包,这个确认包会逆序通过数据流管道回到输出流。输出流在确认了所有数据节点已经写入这个数据包后,就会从对应的缓存队列删除这个数据包。当客户端写满一个数据块之后,就会调用addBlock()申请一个新的数据块,然后循环执行上述操作。
-
关闭输入流并提交文件
当HDFS客户端完成了整个文件中所有数据块的写操作后,就可以调用close()方法关闭输出流,并调用ClientProtocol.complete()方法通知Namenode提交这个文件中的所有数据块,也就完成了整个文件的写入流程
对于Datanode,当Datanode成功地接受一个新的数据块时,Datanode会通过DatanodeProtocol.blockReceivedAndDeleted()方法向Namenode汇报,Namenode会更新内存中的数据块与数据节点的对应关系
如果客户端在写文件时,数据流管道中的Datanode出现故障,则输出流会进行如下操作来进行故障恢复
输出流中缓存的没有确认的数据包会重新加入发送队列,这种机制确保了数据节点出现故障时不会丢失任何数据,所有的数据都是经过确认的。但是输出流会通过调用ClientProtocol.updateBlockForPipeline()方法为数据块申请一个新的时间戳,然后使用这个新的时间戳重新建立数据流管道。这种机制保证了故障Datanode上的数据块的时间戳会过期,然后再故障恢复之后,由于数据块的时间戳与Namenode元数据中的不匹配而被删除,保证了集群中所有数据块的正确性
故障节点会从输入流管道中删除,然后输出流会通过调用ClientProtocol.getAdditionalDatanode()方法通知Namenode分配新的数据节点到数据流管道中。接下来输出流会将新分配的Datanode添加到数据流管道中,并使用新的时间戳重新建立数据流管道。由于新添加的数据节点上没有存储这个新的数据块,这是HDFS客户端会通过DataTransferProtocol通知数据流管道中的一个Datanode复制这个数据块到新的Datanode上
数据流管道重新建立之后,输出流会调用ClientProtocol.updatePipeline()更新Namenode中的元数据。至此一个完成的故障恢复流程就完成了,客户端就可以正常完成后续的写操作了。