client发送块给pipeline上的第一个datanode,此datanode将块数据写到本地并传给下一个。然后将后续块返回来的响应信息加上自身的操作结果信息一起返回到前面。在pipeline上,如果某个DataNode有后续节点,那么,它必须等到后续节点的成功应答,才可以发送应答到它前面的节点。
in:从上层获取数据的入口
repryOut:反馈给上层的确认
mirrorOut:写往下一个datanode
mirrorIn:获取下一个datanode的确认
DataXceiver.writeBlock:
1、首先读取blkId,pipelineSize,target数量等信息;
2、然后构造BlockReceiver(数据块接收器),构造函数中主要是创建数据块的流和校验文件的流;
3、如果不是pipeline的最后一个数据节点,则创建mirrorOut,mirrorIn,并同时将从in读取的数据(blkId,pipelineSize,target数量(除去了本节点),校验类型(client写时只需要最后一个datanode需要校验数据))写往下一个datanode;
4、如果不是pipeline最后datanode节点,通过mirrorIn读取请求应答,并通过ReplyOut将确认包(成功/失败)反馈上层;
5、通过blockReceiver.receiveBlock()发送数据到下一个datanode,并写入本机磁盘;
6、如果本次调用是该Block的最后一个packet,则上报名字节点写数据结束。
下面介绍最重要的receiveBlock()函数:
1、先将校验信息头(校验类型,bytesPerChecksum)写入校验信息文件;
2、如果是client写数据,创建PacketResponder线程,用于从下游接受应答并在合适的时候向上游发送;
3、通过调用receivePacket()接受数据,知道读取完毕为止;//后面分析
4、receivePacket()结束后,往下一个节点写入0表示块写入完成;
5、关闭PacketResponder线程
receivePacket()方法解释:
/**
* 接受一个packet,并保存到本地磁盘,同时发往下一个接收端
*/
private int receivePacket() throws IOException {
//接受一个packet的数据
int payloadLen = readNextPacket();
if (payloadLen <= 0) {
return payloadLen;
}
buf.mark();
//读取packet的头部信息
buf.getInt(); // packet length
offsetInBlock = buf.getLong(); // get offset of packet in block
long seqno = buf.getLong(); // get seqno
boolean lastPacketInBlock = (buf.get() != 0);
int endOfHeader = buf.position();
buf.reset();
setBlockPosition(offsetInBlock);
//First write the packet to the mirror:
if (mirrorOut != null) {
try {
//将packet的头部信息发往下一个接收端
mirrorOut.write(buf.array(), buf.position(), buf.remaining());
mirrorOut.flush();
} catch (IOException e) {
handleMirrorOutError(e);
}
}
buf.position(endOfHeader);
int len = buf.getInt();//packet中数据长度
if (len < 0) {
throw new IOException("Got wrong length during writeBlock(" + block + ") from " + inAddr + " at offset " + offsetInBlock + ": " + len);
}
if (len == 0) {
LOG.debug("Receiving empty packet for block " + block);
} else {
offsetInBlock += len;
//校验数据的长度
int checksumLen = ((len + bytesPerChecksum - 1)/bytesPerChecksum)* checksumSize;
if ( buf.remaining() != (checksumLen + len)) {
throw new IOException("Data remaining in packet does not match " + "sum of checksumLen and dataLen");
}
int checksumOff = buf.position();//校验数据在packet中的开始位置
int dataOff = checksumOff + checksumLen;//真正数据在packet中的开始位置
byte pktBuf[] = buf.array();
buf.position(buf.limit()); // move to the end of the data.
//验证数据
if (mirrorOut == null || clientName.length() == 0) {
verifyChunks(pktBuf, dataOff, len, pktBuf, checksumOff);
}
try {
if (!finalized) {
//将packet中的数据写入磁盘缓存中
out.write(pktBuf, dataOff, len);
// If this is a partial chunk, then verify that this is the only
// chunk in the packet. Calculate new crc for this chunk.
if (partialCrc != null) {
if (len > bytesPerChecksum) {
throw new IOException("Got wrong length during writeBlock(" + block + ") from " + inAddr + " " + "A packet can have only one partial chunk."+ " len = " + len + " bytesPerChecksum " + bytesPerChecksum);
}
partialCrc.update(pktBuf, dataOff, len);
byte[] buf = FSOutputSummer.convertToByteStream(partialCrc, checksumSize);
checksumOut.write(buf);
LOG.debug("Writing out partial crc for data len " + len);
partialCrc = null;
} else {
//将packet中的校验数据写入磁盘缓存
checksumOut.write(pktBuf, checksumOff, checksumLen);
}
datanode.myMetrics.bytesWritten.inc(len);
}
} catch (IOException iex) {
datanode.checkDiskError(iex);
throw iex;
}
}
//将packet的数据和校验数据flush到磁盘
flush();
//将该packet交给PacketResponder来确认
if (responder != null) {
((PacketResponder)responder.getRunnable()).enqueue(seqno, lastPacketInBlock);
}
if (throttler != null) { // throttle I/O
throttler.throttle(payloadLen);
}
return payloadLen;
}
(1) 内部首先调用readNextPacket()读取至少一个完整的packet
(2) setBlockPosition:由于新写入的数据的起始位置位于校验块的中间,此时需要计算不完整数据块的长度,然后将这些数据读入并计算校验值。后面将新写入的数据一起,计算出新的校验值,并覆盖写到校验信息文件中。而实际数据则追加到尾部即可。
(3) 将包中的数据写到下游数据节点中
(4) 如果是pipeline的最后一个节点,校验数据
(5) 在第(2)步设置好的位置,写入磁盘
(6) 将本次seqno的信息enqueue到PacketResponse线程的ackQueue中
(7) 如果本节点不是pipeline的最后一个节点,则需要read下游节点的返回ack
(8) 如果本节点是写请求的最后一个数据包的确认,则关闭接收器流,finalizeBlock(),上报namenode等
(9) 构造replies[],向上游返回状态。interrupt该BlockReceiver线程。