一、创建文件
1、DFSClient.create()用于创建一个空文件,返回一个输出流对象。在函数内部,会构造一个DFSOutputStream,它主要是通过namenode的RPC方法,创建一个文件到namenode。
2、此构造函数还会计算一个包中最大可以放的数据。一般来说,数据包最大能达到64K,计算如下:
int chunkSIze = csize + checksumSize;//如512字节实际数据计算出一个校验和4字节数据,512 + 4
int n = 包头
chunkPerPacket = Math.max((64K - n + chunkSize - 1 )/chunkSize, 1);//即((64K-包头 - 1)/(512+4)) + 1,即一个包内可存放多少个chunk数据,不足一个的算一个
packetSize = n + chunkSize*chunkPerPacket;//整个包的size
3、构造函数中还会开启一个DataStream线程,它用于获取新的数据块,建立与数据节点的数据流管道,往数据界定发送数据,接受应答等
二、数据流管道建立
1、在run函数中,循环从dataqueue队列中取出packet包,通过调用nextBlockOutputStream建立与下一个数据块的输出流,并将该packet包加入ackQueue中,用于接受应答.所以,真正与数据节点建立的流blockStream在run()函数中建立,作为DFSOutputStream成员变量。
2、取出packet中的数据,写往数据块输出流中。如果本次写是该Block的最后一个packet包,则需要发送一个0,标示结束,blockStream.writeInt(0)。通过flush强制将缓冲区数据刷到流中。
3、nextBlockOutputStream:调用namenode RPC函数AddBlock,向namenode申请一个块LocatedBlock,包含多副本的多个DatanodeInfo信息。然后建立于第一个DN的tcp连接。
三、写数据
1、write(byte b[],int off,int len)=>循环调用write1()将len长度的数据都写进去=>writeChecksumChunk()=>writeChunk:如下
2、构造currentPacket对象,将本次调用的checksum及data写入currentPacket对象。
3、上层循环调用write(),不断将 512字节数据及校验和数据写入currentPacket对象。始终保持校验在前,实际数据在后。
4、当包被写满或者达到块大小时,将currentPacket放入dataQueue队列中,用于线程函数获取并发送。
5、ResponseProcessor处理写应答
四、数据流管道出错处理
1、如果数据流管道出错,需要将数据流连接关闭,将数据包从ackQueue移动到dataQueue;
2、将出现故障的节点从列表中移除,并选择一个恢复的主数据节点,通过datanode的RPC方法recoverBlock()发起块恢复。如果恢复出错又有重新选择其他节点的机会,则重新选择;否则出错。如果恢复成功,则可以继续写数据。这时通过createBlockOutputStream重新创建数据流管道,重复前面所述的步骤。客户端通过此种机制,保证在写的过程中出现错误,能够剔除问题节点,并继续写文件,降低错误数据节点的影响。
五、关闭输出流
DFSOutputStream.close()方法:首先将缓存中的数据刷入包中,并等待所有数据包的应答返回。然后发送结束写标志“0”,向namenode关闭文件。
至此,HDFS输出流介绍完毕。