1

HDFS的几个典型的流程:客户端读HDFS文件流程、客户端写HDFS文件流程、客户端追加写HDFS文件流程、数据节点与名字节点交互流程以及HDFS HA切换流程等。

一、客户端读HDFS文件流程
  1. 打开HDFS文件:HDFS客户端首先调用DistributedFileSystem.open()方法打开HDFS文件,这个方法在底层会调用ClientProtocol.open()方法,该方法会返回一个HdfsDataInputStream对象用于读取数据块。HdfsDataInputStream其实是一个DFSInputStream的装饰类,真正进行数据块读取操作的是DFSInputStream对象。
  2. 从Namenode获取Datanode地址:在DFSInputStream的构造方法中,会调用ClientProtocol.getBlockLocations()方法向名字节点获取该HDFS文件起始位置数据块的位置信息。Namenode返回的数据块的存储位置是按照与客户端的距离远近排序的,所以DFSInputStream可以选择一个最优的Datanode节点,然后与这个节点建立数据连接读取数据块。
  3. 连接到Datanode读取数据块:HDFS客户端通过调用DFSInputStream.read()方法从这个最优的Datanode读取数据块,数据会以数据包(packet)为单位从数据节点通过流式接口传送到客户端。当达到一个数据块的末尾时,DFSInputStream就会再次调用ClientProtocol.getBlockLocations()获取文件下一个数据块的位置信息,并建立和这个新的数据块的最优节点之间的连接,然后HDFS客户端就可以继续读取数据块了。
  4. 关闭输入流:当客户端成功完成文件读取后,会通过HdfsDataInputStream.close()方法关闭输入流。

流程图如下:

QQ图片20200916122252.png

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

二、客户端写HDFS文件流程
  1. 创建文件: HDFS客户端写-一个 新的文件时,会首先调用DistributedFileSystem.create()方法在HDFS文件系统中创建一个新的空文件。这个方法在底层会通过调用ClientProtocol.create()方法通知Namenode执行对应的操作, Namenode会首先在文件系统目录树中的指定路径下添加一个新的文件,然后将创建新文件的操作记录到editlog中。完成ClientProtocol.create()调用后, DistributedFileSystem.create()方法就会返回一个HdfsDataOutputStream对象,这个对象在底层包装了一个DFSOutputStream对象,真正执行写数据操作的其实是DFSOutputStream对象。
  2. 建立数据流管道:获取了DFSOutputStream对象后,HDFS客户端就可以调用 DFSOutputStream.write()方法来写数据了。由于DistributedFileSystem.create()方法只是在文件系统目录树中创建了一个空文件,并没有申请任何数据块,所以DFSOutputStream会首先调用ClientProtocol.addBlock()向Namenode申请一个新的空数据块,addBlock()方法会返回一个LocatedBlock对象,这个对象保存了存储这个数据块的所有数据节点的位置信息。获得了数据流管道中所有数据节点的信息后,DFSOutputStream就可以建立数据流管道写数据块了。
  3. 通过数据流管道写入数据: 成功地建立数据流管道后,HDFS客户端就可以向数据流管道写数据了。写入DFSOutputStream中的数据会先被缓存在数据流中,之后这些数据会被切分成一个个数据包(packet)通过数据流管道发送到所有数据节点。通过数据流管道依次写入数据节点的本地存储。每个数据包都有个确认包(ack),确认包会逆序通过数据流管道回到输出流。输出流在确认了所有数据节点已经写入这个数据包之后,就会从对应的缓存队列删除这个数据包。当客户端写满一个数据块之后,会调用addBlock()申请一个新的数据块,然后循环执行上述操作。
  4. 关闭输入流并提交文件: 当HDFS客户端完成了整个文件中所有数据块的写操作之后,就可以调用close()方法关闭输出流,并调用ClientProtocol.complete()方法通知Namenode提交这个文件中的所有数据块,也就完成了整个文件的写入流程。

流程图如下:
image.png

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

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

  1. 输出流中缓存的没有确认的数据包会重新加入发送队列,这种机制确保了数据节点出现故障时不会丢失任何数据,所有的数据都是经过确认的。输出流会通过调用ClientProtocol.updateBlockForPipeline()方法为数据块申请一个新的时间戳,然后使用这个新的时间戳重新建立数据流管道。这种机制保证了故障Datanode上的数据块的时间戳会过期,然后在故障恢复之后,由于数据块的时间戳与Namenode元数据中的不匹配而被删除,保证了集群中所有数据块的正确性。
  2. 故障数据节点会从输入流管道中删除,然后输出流会通过调用ClientProtocol.getAdditionalDatanode()方法通知Namenode分配新的数据节点到数据流管道中。接下来输出流会将新分配的Datanode添加到数据流管道中,并使用新的时间戳重新建立数据流管道。由于新添加的数据节点上并没有存储这个新的数据块,这时HDFS客户端会通过DataTransferProtocol通知数据流管道中的一个Datanode复制这个数据块到新的Datanode上.
  3. 数据流管道重新建立之后,输出流会调用ClientProtocol.updatePipeline()更新Namenode中的元数据。至此,一个完整的故障恢复流程就完成了,客户端可以正常完成后续的写操作了。
三、Datanode启动、心跳以及执行名字节点指令流程

Datanode启动后与 Namenode 的交互主要包括三个部分:①握手;②注册;③块汇报以 及缓存汇报。

  1. Datanode启动时会首先通过DatanodeProtocol.versionRequest()获取Namenode的版本号以及存储信息等,然后Datanode会对Namenode的当前软件版本号和Datanode的当前软件、版本号进行比较,确保它们是一致的。
  2. 成功地完成握手操作后,Datanode会通过DatanodeProtocol.register()方法向Namenode注册。Namenode接收到注册请求后,会判断当前Datanode的配置是否属于这个集群,它们之间的版本号是否一致。
  3. 注册成功之后,Datanode就需要将本地存储的所有数据块以及缓存的数据块上报到Namenode,Namenode会利用这些信息重新建立内存中数据块与Datanode之间的对应关系。

至此,Datanode 就完成了启动的所有操作,之后就可以正常对外服务了。

Datanode成功启动之后,需要定期向Namenode发送心跳,让Namenode知道当前Datanode 处于活动状态能够对外服务。Namenode 会在Datanode 的心跳响应中携带名字节点指令,指导Datanode进行数据块的复制、删除以及恢复等操作。

当Datanode成功地添加了一个新的数据块或者删除了-一个已有的数据块时,需要通过 DatanodeProtocol.blockReceivedAndDeleted(方法向Namenode汇报。Namenode接收到这个汇 报之后,会更新Namenode内存中数据块与数据节点之间的对应关系。


小明的数据脚印
145 声望41 粉丝