ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Hudi Log 文件格式与读写流程

2021-12-25 14:32:01  阅读:245  来源: 互联网

标签:Hudi Log 写入 content 文件格式 字节 block size


Hudi Log 文件格式与读写流程

背景

对 Hudi 有一定了解的读者应该知道,Hudi 有 COW 和 MOR 两种表类型。其中的 MOR 表会通过日志文件记录文件,写入一个 MOR 表后产生的文件可以观察到,一个 MOR 表数据存储在3种文件中:Log File(.*log.*),分区元数据(.hoodie_partition_metadata),数据文件(.*parquet)。期间查阅了大量的相关资料,发现发现鲜有文章对 Log 日志数据文件的具体内容与结构做详细的介绍。原因之一可能是:虽然 Log 文件使用了 Avro 序列化的行式存储,但其仅在对 records 的序列化时使用了原生 avro 的标准,文件其余部分的编码为 Hudi 自己的格式,进而导致其无法使用avro-tools.jar对其进行查看。本文介绍内容如下:

  • Log 文件读写流程
  • Log 文件结构分析

Log 文件读写流程

本文基于 Hudi 0.9.0 版本进行分析。形如Log File(.*log.*)的日志文件,由于无法直接使用工具查看内容,所以主要从源码的层面分析日志文件的写入和读取内容。

写入流程

整个写入流程的代码实现在:org.apache.hudi.common.table.log.HoodieLogFormatWriter#appendBlocks

for (HoodieLogBlock block: blocks) {
  long startSize = outputStream.size();

  // 1. Write the magic header for the start of the block
  outputStream.write(HoodieLogFormat.MAGIC);

  // bytes for header
  byte[] headerBytes = HoodieLogBlock.getLogMetadataBytes(block.getLogBlockHeader());
  // content bytes
  byte[] content = block.getContentBytes();
  // bytes for footer
  byte[] footerBytes = HoodieLogBlock.getLogMetadataBytes(block.getLogBlockFooter());

  // 2. Write the total size of the block (excluding Magic)
  outputStream.writeLong(getLogBlockLength(content.length, headerBytes.length, footerBytes.length));

  // 3. Write the version of this log block
  outputStream.writeInt(currentLogFormatVersion.getVersion());
  // 4. Write the block type
  outputStream.writeInt(block.getBlockType().ordinal());

  // 5. Write the headers for the log block
  outputStream.write(headerBytes);
  // 6. Write the size of the content block
  outputStream.writeLong(content.length);
  // 7. Write the contents of the data block
  outputStream.write(content);
  // 8. Write the footers for the log block
  outputStream.write(footerBytes);
  // 9. Write the total size of the log block (including magic) which is everything written
  // until now (for reverse pointer)
  // Update: this information is now used in determining if a block is corrupt by comparing to the
  //   block size in header. This change assumes that the block size will be the last data written
  //   to a block. Read will break if any data is written past this point for a block.
  outputStream.writeLong(outputStream.size() - startSize);

  // Fetch the size again, so it accounts also (9).
  sizeWritten +=  outputStream.size() - startSize;
}

总结为流程图如下:

截屏2021-12-25 上午11.47.38

从图中可以看出,写入内容逻辑比较清晰:

  1. 写入Magic,内容为#HUDI#
    • 拿到header,footer的字节数组。header/footer类型为Map。字节数组中会写入此Map的size,Map的Key(enum类型)的序号,Value为String类型
    • 拿到content的字节数组。会序列化records,字节数组中会写入log block version,records的数量和avro序列化后的records
  2. 写入block size,不包括Magic的6个字节。
  3. 写入log format version。
  4. 写入block type,为枚举类型,写入的是枚举的序号转化为字节数组。
  5. 写入header字节数组。
  6. 写入content length。
  7. 写入content字节数组。
  8. 写入footer字节数组。
  9. 写入block length,包括Magic的6个字节。

读取流程

整个读取流程的代码实现在:org.apache.hudi.common.table.log.HoodieLogFileReader#readBlock

总结为流程图如下:

截屏2021-12-25 上午11.48.24

在读取流程中,会涉及一些校验的动作:

  1. 在hasNext()方法中,读取Magic。
    • 校验Magic。不相等,抛CorruptedLogFileException异常;相等,返回true;EOF到达文件尾部,吃掉异常,返回false。
  2. 读block size。
    • 校验block是否损坏。方式为指针移动 block size减去long类型字节长度 的位置,读取block length,比较 block size与block length减去Magic字节长度 是否相等。不相等,createCorruptBlock;相等继续。
  3. 读log format version。
  4. 读block type。
  5. 读header。
  6. 读content length。
  7. 读content。
    • 不启用readBlockLazily,从内存度content;启用readBlockLazily,指针移动到content结束位置。
  8. 读footer。
  9. 读block length。

Log 文件结构分析

Hudi 中与日志文件有关类的继承关系可以看下图:截屏2021-12-25 下午1.00.38

可以看出,Log 文件的公共部分属性主要在HoodieLogBlock中进行定义,数据部分的属性主要在HoodieLogBlock中进行定义,数据部分content的序列化与反序列化实现在HoodieAvroDataBlock中定义。

从上述读写流程,也可以总结出 Log 文件结构:

截屏2021-12-25 上午10.50.23

  • Magic:#HUDI#,字节数组类型。
  • blcok size:除了Magic的6字节长度和自身long类型8字节长度,long类型。
  • log format version:日志格式版本,现在是1,int类型。
  • block type:本身是枚举类型,有COMMAND_BLOCK, DELETE_BLOCK, CORRUPT_BLOCK, AVRO_DATA_BLOCK, HFILE_DATA_BLOCK,这里为枚举的序号,int类型。
  • header:字节数组,本身是Map,转换为字节数组时会将map的size写入字节数组。map的Key为HeaderMetadataType类型,是个枚举,有INSTANT_TIME, TARGET_INSTANT_TIME, SCHEMA, COMMAND_BLOCK_TYPE,Value为String类型。
  • content length:content字节数组的长度,long类型。
  • content:字节数组。其中内容为,log block version,int类型;records number,int类型;records序列化的字节数组。
  • footer:字节数组,和header一样,本身是Map,现在版本还没有内容。
  • block length:block size 加上6(Magic的字节数组长度),long类型。

根据 Log 文件的读取逻辑,不难写出解析 Log 日志文件的程序代码。将 Log 文件解析出来后,可以观察到 block length 的数值相较于文件本身大小,相差了8个字节,这8个字节就是 block size 本身long类型的字节长度,进一步证明了上述分析的正确性

总结

至此,对 MOR 表的Log File(.*log.*)文件分析已经全部结束,文章着重分析了文件结构,对读写流程中额外的异常处理没有进一步深入。文中如有遗漏或错误,欢迎指出讨论。

若读完有所收获,欢迎点赞吖~

标签:Hudi,Log,写入,content,文件格式,字节,block,size
来源: https://blog.csdn.net/zhuzilong2013/article/details/122143051

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有