hadoop初读–Packet.getBuffer()
要回答的飘过的疑惑: 如果创建一个文件,就写入一行几个字节的”helloword”,如何组装一个packet及如何写出?
1.如何组装: out.write(str.getBytes()); 方法调用后数据拷贝到buf[]中.
2.如何写出(写出到DataNode):
1) 关键在于调用了out.close()方法.
out.close()—>out.flushBuffer()—>out.closeInternal()
—>out.flushInternal()—>out.enqueueCurrentPacket().
这一系列时序方法后,将buf[]里的数据刷到currentPacket里,
且设置成最后一个数据包lastPacketInBlock = true,扔到dataQueue里.等待dataStreamer去发送.
2) dataStreamer.run()方法.
关键在于调用了ByteBuffer buf= one.getBuffer();
getBuffer()方法将小字节数据组装成符合packet格式的包,然后发送.
/**
应用代码
*/
String str = "Hello world";
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(configuration);
java.io.OutputStream out = fs.create(new Path("/helloworld"));
out.write(str.getBytes());
out.flush();
out.close();
下面主要结合小数据(写不满一个packet)或者lastPacket的场景记录下getBuffer方法实现,主要还是以注释和图解的方式.
ByteBuffer getBuffer() {
if (buffer != null) {
return buffer;
}
//chunkData长度
int dataLen = dataPos - dataStart;
//checkSumData长度
int checksumLen = checksumPos - checksumStart;
//如果检验数据和数据不是连续的,则将校验数据往后移动,使得检验数据和数据是连续的,
//之所以移动检验数据而不移动数据主要是应为检验数据一般要比数据少得多,这样移动的开销也就小
if (checksumPos != dataStart) {
```(1)```
System.arraycopy(buf, checksumStart, buf, dataStart - checksumLen , checksumLen);
}
int pktLen = SIZE_OF_INTEGER + dataLen + checksumLen;
```(2)```
buffer = ByteBuffer.wrap(buf, dataStart - checksumPos, DataNode.PKT_HEADER_LEN + pktLen);
buf = null;
```(3)```
buffer.mark();
```(4) 一系列put方法```
//整个数据包的长度(4+校验数据长度+数据长度)(4)
buffer.putInt(pktLen);
//数据包的数据在数据块中的偏移位置(8)
buffer.putLong(offsetInBlock);
//数据包在数据块中的序列号(8)
buffer.putLong(seqno);
//是否是数据块中的最后一个数据包(1)
buffer.put((byte) ((lastPacketInBlock) ? 1 : 0));
//chunkData长度
buffer.putInt(dataLen);
```(5)```
buffer.reset();
return buffer;
}
}
下面结合手画的半抽象数据包packet,记录下代码实现.先就图做简要说明.
为了画的线短一点,假设5个chunk就可以写满一个packet,图是根据有针对性的情况:只写入小数据(一个chunk)的情况.
- “4”代表一个checksumdata(长度),紧跟其后的4个”x”代表未写入任何数据的空间;
- “512”代表一个chunkdata(长度),紧跟其后的4个”x”代表未写入任何数据的空间.
- “25”代表初始化时预留给header的位置点.
- 图0是初始化后、未做任何操作前的packet
- 图1是执行了arraycopy方法后的packet
- 图2是wrap方法后的packet
- 图3是mark方法后的packet
- 图4是put(多个put,header)后的packet
- 图5是reset方法后的packet
wrap方法前,即0、1步主要关注checksumdata如何移动;
wrap方法开始,即2、3、4、5步主要关注mark和position的位置,因为最后写出数据是从position位置(position前其实是空的,没有写入任何数据)开始写的.
0步骤: packet(普通包)初始化后的状态,见构造函数.
1步骤: 为了checksumdata和checkdata紧邻,通过arraycopy进行数据移动,将”25”字节位置后的4字节checksumdata移到了”512”字节chunkdata位置的前面. 将参数数字化后: System.arraycopy(buf, 25, buf, dataStart - 4 , 4), dataStart-4意味着dataStart前移4个字节的位置.
2步骤: 通过wrap方法,包装出一个新的数组,ByteBuffer的position位于16个字节位置.将参数数字化后:ByteBuffer.wrap(buf, 25, buf, 45 - 29(=16) , pktLen).
3步骤: 通过mark方法记录开始的position.
4步骤: 通过一系列的put方法设置header信息,见图中的大括号,占了25个字节.这时position移到了16+25(=41)的位置.
5步骤: 通过reset方法,将position重设为mark标记的开始的位置,后续write就position位置开始写出.