HDFS客户端写操作与输出流详解

客户端在执行文件写操作前,首先需要调用DistributedFileSystem.create()创建一个空的HDFS文件,然后create()方法会调用DFSClient.create()方法创建DFSOutputStream对象,并将这个对象包装成HDFSDataOutputStreams输出流,这样客户端就可以在输出流HDFSDataOutputStream对象上调用write方法执行写操作了。并且该create()方法还会调用beginFileLease()方法获取文件的租约。

在DFSOutputstream中使用使用Packet类来封装一个数据包,每个数据包中都包含若干个校验块,以及校验快对应的校验和。DFSOutputStream.write()方法可以将指定大小的数据写入数据流内部的一个缓冲区中,写入的数据被切分成若干个数据包,每个数据包又是由一组校验块和这组校验块对应的校验和组成的,默认的数据包的大小是65536字节,校验快大小为512字节,每个校验和都是校验块的512字节对应的校验值。

当Client写入的字节流数据达到一个数据包的长度时,DFSOutputStream会构造一个Packet对象保存这个要发送的数据包。如果当前数据块的所有数据包都发送完毕了,DFSOutputStream会发送一个空的数据包标识数据块发送完毕。新构造的Packet对象会被放到DFSOutputStream.dataqueue队列中,由DFSOutputStream的内部线程类DataStreamer处理。

DataStreamer线程首先会向Nmaenode申请一个新的数据块,然后建立写这个数据块的数据流管道,最后会从dataQueue中取出Packet对象,然后通过底层IO流将这个Packet发送到数据流管道的第一个Datanode上。发送完毕后,将Packet从dataQueue中移出,放入到ackqueue中等待下游节点的确认消息。确认消息是由DataStreamer的内部线程类ResponseProcessor处理的。

ResponseProcessor线程等待下游节点的响应ack,判断ack的状态码,如果是失败状态,则记录出错的Datanode的索引。如果ack状态时成功,则将数据包从ackqueue中删除,整个数据包发送过程完成。当一个数据块的所有数据包都发送完毕时,并且获得了ACK响应之后,DataStreamer就会将当前数据块的数据流管道关闭,如果还有数据需要发送,则DataStreamer会再次向Namenode申请分配新的数据块,并且提交上一个数据块。然后再次建立新的数据流管道,发送数据。

如果数据块发送过程中出现错误,那么所有ackqueue队列中等待确认的Packet都会被重新放到dataQueue队列中重新发送,客户端会执行错误处理流程,将出现错误的Datanode从数据流管道中删除,然后向namenode申请新的Datanode重建数据流管道。接着DataStreamer线程会从dataQueue队列中取出packet重新发送。

关闭输出流
DFSOutputStream.close()方法首先调用FlushBuffer()将输出流中缓存的数据写入数据包,然后将数据流中没有发送的数据包放入dataQueue队列中,最后构造一个新的空间数据包用于标识数据块已经全部写完。close()方法确认所有的数据包都成功的写入了数据流管道之后,就会调用ClientProtocol.complete()方法向Namenode提交这个文件。最后close方法会释放当前文件的租约。

你可能感兴趣的:(HDFS源码)