Hadoop-剖析HDFS读写流程

HDFS客户端读流程

打开HDFS文件:HDFS客户端首先调用DistributedFileSystem.open()方法打开HDFS文件,这个方法在底层会调用ClientProtocol.open()方法,该方法返回一个HdfsDataInputStream对象用于读取数据块。HdfsDataInputStream其实是一个DFSInputStream的装饰类,真正进行数据块读取操作的是DFSInputStream对象。

从Namenode获取Datanode地址:在DFSInputStream的构造方法中,会调用ClientProtocol.getBlockLocations()方法向Namenode获取该HDFS文件其实位置数据块的位置信息。Namenode返回的数据库的存储位置是按照与客户端的距离远近排序的,所以DFSInputStream可以选择一个最优的Datanode节点,然后与这个节点建立数据连接,读取数据块。

连接到Datanode读取数据块:HDFS客户端通过调用DFSInputStream.read()方法从这个最优的Datanode读取数据块,数据会以数据包(packet)为单位从数据节点通过流式接口传送到客户端。当达到一个数据块的末尾时,DFSInputStream就会再次调用ClientProtocol.getBlockLocations()获取下一个数据块的位置信息,并建立和这个新的数据块的最优节点之间的连接,然后HDFS客户端就可以继续读取数据块了。

关闭输入流:当客户端完成文件读取之后,会通过HdfsDataInputStream.close()方法关闭输入流。

 

客户端读取数据块时,很有可能存储这个数据块的数据节点出现异常,也就是无法读取数据。出现这种情况时,DFSInputStream会切换到另一个保存了这个数据块副本的数据节点,然后读取数据。同时需要注意的是,数据包中不但包含了数据,还包含了校验和。HDFS客户端接收到数据包时,会对数据进行校验,如果出现校验错误,也就是数据节点上的这个数据块副本出现了损坏,HDFS客户端会通过ClientProtocol.reportBadBlocks()向Namenode汇报这个损坏的数据块副本,同时DFSInputStream会尝试从其他的数据节点读取这个数据块。

 

 

 

HDFS客户端写流程

扫描二维码关注公众号,回复: 4920075 查看本文章

创建文件:HDFS客户端写一个新的文件时,会首先调用DistributedFileSystem.create()方法在HDFS文件系统中创建一个新的空文件。这个方法会在底层通过调用ClientProtocol.create()方法通知Namenode执行对应的操纵,Namenode会首先在文件系统目录中的指定路径下添加一个新的文件,此时,Namenode会检查是否存在这个文件和客户端是否拥有创建这个文件的权限,检查通过,将创建新文件的操作记录到editlog中,如果检查未通过,则向客户端抛出一个IOException异常。完成ClientProtocol.create()调用后,DistribuFileSystem.create()方法就会返回一个HdfsDataOutputStream对象,这个对象在底层包装了一个DFSOutputStream对象,真正执行写数据操作的是DFSOutputStream对象。

建立数据流通道:获取了DFSOutputStream对象后,HDFS客户端就可以调用DFSOutputStream.write()方法来写数据了。由于DistributedFileSystem.create()方法只是在文件系统目录树中创建了一个空文件,并没有申请任何数据块,所以DFSOutputStream会首先调用ClientProtocol.addBlock()向Namenode申请一个新的空数据块,addBlock()方法会返回一个LocatedBlock对象,这个对象保存了存储这个数据块的所有数据节点的位置信息。获得了数据流管道中所有数据节点的信息后,DFSOutputStream就可以建立数据流管道写数据块了。

通过数据流管道写入数据:成功建立数据流管道后,HDFS客户端就可以向数据流管道写数据了。写入DFSOutputStream中额数据会先被缓存在数据流中,之后这些数据会被切分成一个个数据包(packet)通过数据流管道发送到所有数据节点。这里的每个数据包都会通过数据流管道依次写入数据节点的本地存储。每个数据包都有个确认包,确认包会逆序通过数据流管道回到输出流。输出流在确认了素有数据节点已经写入这个数据包后,就会从对应的缓存队列删除这个数据包。当客户端写满一个数据块之后,会调用addBlock()申请一个新的数据块,然后循环执行上述操作。

关闭输出流并提交文件:当HDFS客户端完成了整个文件中所有数据块的写操作之后,就可以调用close()方法关闭输出流,并调用ClientProtocol.complete()方法通知Namenode提交这个文件中的所有数据块,完成写流程。

 

对于Datanode,当Datanode成功接受一个新的数据块时,Datanode会通过DatanodeProtocol.blockReceivedAndDelete()方法向Namenode汇报,Namenode会更新内存中的数据库与数据节点的对应关系。

 

如果客户端在写文件时,数据流管道中的数据节点出现故障,则输出流会进行如下操作来进行故障恢复:

输出流中缓存的没有确认的数据包会重新加入发送队列,这种机制确保了数据节点出现故障时不会丢失任何数据,所有的数据都是经过确认的。但是数据流会通过调用ClientProtocol.updateBlockForPipeline()方法为数据库申请一个新的时间戳,然后使用这个新的时间戳重新建立数据流管道。这种机制保证了故障Datanode上的数据库的时间戳会过期,然后在故障恢复之后,由于数据块的时间戳与Namenode元数据中的不匹配二被删除,保证了集群中所有数据块的正确性。

故障数据节点会从输入流管道中删除,然后输出流会通过调用ClientProtocol.getAdditionalDatanode()方法通知Namenode分配新的数据节点到数据流管道中。接下来输出流会将新分配的Datanode添加到数据流管道中,并使用新的时间戳重新建立数据流管道。由于新添加的数据节点上并没有存储这个新的数据块,这时HDFS客户端会通过DaraTransferProtocol通知数据流管道中的一个Datanode复制这个数据块到新的Datanode上。

数据流管道重新建立之后,输出流会调用ClientProtocol.updatePipeline()更新Namenode中的元数据。至此,一个完整的故障恢复流程就完成了,客户端可以正常完成后续的写操作了

猜你喜欢

转载自blog.csdn.net/qq_38741415/article/details/86486676