前言
上篇文章Ozone Datanode的分布式元数据管理,笔者阐述了关于Ozone Datanode的分布式元数据相关的内容以及Datanode上的数据layout。既然我们了解了Datanode上元数据的结构,那么一个很自然的问题:Datanode如何进行数据的写入的呢?里面的数据一致性是怎么做的?中间写数据发生错误,Datanode这边怎么来处理?本文我们来细聊Ozone数据写入的内部过程。
Ozone Key(文件)数据的写入
我们知道,Ozone作为对象存储系统,支持K-V模式的键值对存储,文件的数据的put操作实质上代表的key的写入。Ozone在这边是通过OM返回给客户端KeyOutputStream对象,来进行后续数据的写入的。换句话说,client是通过向OM服务申请获取目标key的KeyOutputStream,样例代码如下:
private void writeKey(String key1, OzoneBucket bucket) throws IOException {
OzoneOutputStream out = bucket.createKey(key1, 1024, STAND_ALONE,
ONE, new HashMap<>());
out.write(RandomStringUtils.random(1024).getBytes());
out.close();
}
OzoneOutputStream内部包装的OutputStream对象即为KeyOutputStream。
KeyOutputStream代表一个key下的数据文件,如果目标key对应要写的数据文件比较大,就可能会出现需要多block存储的情况。类似于HDFS大文件,需要多个block来存储,每个block根据offset来分隔开。在Ozone中,每个block又对应有独自的BlockOutputStream,这里每个专属的BlockOutputStream全程控制对应词block的数据写操作。
在前篇文章也提到了,Block在Ozone中是虚拟的概念,实际存储的文件叫chunk文件,一个Block由1个或多个chunk文件组成。因此BlockOutputStream的数据写出实质上是chunk文件的数据写出。BlockOutputStream内部维护了一个Chunk Buffer池做临时数据缓存,等数据达到flush触发阈值,BlockOutputStream再进行chunk文件的数据写Datanode操作。
然后执行client向Datanode发起putBlock的元数据更新操作,更新其Container db文件。此操作结束,意味着这个block数据成功地被写出Datanode中了。然后BlockOutputStream内部对应的Chunk Buffer空间也能被释放了。
上述数据的写出过程如下图所示:
KeyOutputStream的write方法代码如下,通过Block池创建多个BlockOutputStream进行数据的写入,
private void handleWrite(byte[] b, int off, long len, boolean retry)
throws IOException {
while (len > 0) {
// 如果当前剩余写入长度还未减少为0,则意为数据还未完全写出到Block,则继续进行循环内的数据写出
try {
// 1.Block Pool新申请块进行数据的写入,返回的BlockOutputStream包装对象
BlockOutputStreamEntry current =
blockOutputStreamEntryPool.allocateBlockIfNeeded();
// length(len) will be in int range if the call is happening through
// write API of blockOutputStream. Length can be in long range if it
// comes via Exception path.
// 2.计算得到应写出的len数据长度,取当前BlockOutputStream和目标写入长度的最小值
int writeLen = Math.min((int) len, (int) current.getRemaining());
long currentPos = current.getWrittenDataLength();
// 3.写出字节数据到BlockOutputStream,数据范围为字节b从offset位置后的writeLen长度
// 此过程如果达到内部buffer触发阈值,会进行chunk的flush写出。
writeToOutputStream(current, retry, len, b, writeLen, off, currentPos);
// 4.如果写完这批数据后,此BlockOutputStream达到最大写入length限制,无剩余,则close此stream
// close操作会flush出最后一个block chunk文件。
if (current.getRemaining() <= 0) {
// since the current block is already written close the stream.
handleFlushOrClose(StreamAction.FULL);
}
// 5.更新offset和len长度值
len -= writeLen;
off += writeLen;
} catch (Exception e) {
// 6.如果发生异常,关闭当前在写的stream
markStreamClosed();
throw new IOException("Allocate any more blocks for write failed"