乐趣区

关于消息中间件:重新理解RocketMQ-Commit-Log存储协议

本文作者:李伟,社区里大家叫小伟,Apache RocketMQ Committer,RocketMQ Python 客户端我的项目 Owner,Apache Doris Contributor,腾讯云 RocketMQ 开发工程师。

最近忽然感觉:很多软件、硬件在设计上是有 root reason 的,不是 by desgin 如此,而是解决了那时、那个场景的那个需要。一旦理解后,就会感觉在和设计者对话,理解他们的思路,学习他们的办法,思维同屏:活到老学到老。

1 大家思考

1.1 Consumer Queue Offset 是间断的吗,为什么?

1.2 Commit Log Offset 是间断的吗,为什么?

1.3 Java 写的文件,默认是大端序还是小端序,为什么?

2Commit Log 实在散布

在大家思考之际,咱们回忆下 commit log 是怎么散布的呢?

在 Broker 配置的存储根目录下,通过查看 Broker 理论生成的 commit log 文件能够看到相似上面的数据文件散布:

Broker 实在数据文件存储散布

能够看到,实在的存储文件有多个,每一个都是以一串相似数字的字符串作为文件名的,并且大小 1G。

咱们联合源码能够晓得,理论的形象模型如下:

Commit Log 存储文件散布形象

由上图得悉:

  • Commit Log 是一类文件的称说,实际上 Commit Log 文件有很多个,每一个都能够称为 Commit Log 文件。

如图中示意了总共有 T 个 Commit Log 文件,他们依照由过来到当初的创立工夫排列。

  • 每个 Commit Log 文件都保留音讯,并且是依照音讯的写入程序保留的,并且总是在写创立工夫最大的文件,并且同一个时刻只能有一个线程在写。

如图中第 1 个文件,1,2,3,4… 示意这个文件的第几个音讯,能够看到第 1234 个音讯是第 1 个 Commit Log 文件的最初一个音讯,第 1235 个音讯是第 2 个 Commit Log 的第 1 个音讯。

阐明 1:每个 Commit Log 文件里的全副音讯理论占用的存储空间大小 <=1G。这个问题大家自行思考下起因。

阐明 2:每次写 Commit Log 时,RocketMQ 都会加锁,代码片段见 https://github.com/apache/rocketmq/blob/7676cd9366a3297925deabcf27bb590e34648645/store/src/main/java/org/apache/rocketmq/store/CommitLog.java#L676-L722

append 加锁

咱们看到 Commit Log 文件中有很多个音讯,依照既定的协定存储的,那具体协定是什么呢,你是怎么晓得的呢?

3Commit Log 存储协定

对于 Commit Log 存储协定,咱们问了下 ChatGPT,它是这么回复我的,尽管不对,然而这个回复格局和阐明曾经十分靠近答案了。

ChatGPT 回复

咱们翻看源码,具体阐明下:https://github.com/apache/rocketmq/blob/rocketmq-all-4.9.3/store/src/main/java/org/apache/rocketmq/store/CommitLog.java#L1547-L1587

Commit Log 存储协定

我整顿后,如下图;

我了解的 Commit Log 存储协定

阐明 1:我整顿后的音讯协定编号和代码中不是统一的,代码中只是表明了程序,实在物理文件中的存储协定会更具体。

阐明 2:在我写的《RocketMQ 分布式消息中间件:外围原理与最佳实际》中,这个图短少了 Body 内容,这里加了,也更具体的补充了其余数据。

这里有几个问题须要阐明下:

  • 二进制协定存在字节序,也就是常说的大端、小端。大小端这里不具体阐明感兴趣的同学本人 google 或者问 ChatGPT,答复必定比我说的好。
  • 在 java 中,一个 byte 占用 1 个字节,1 个 int 占用 4 个字节,1 个 short 占用 2 个字节,1 个 long 占用 8 个字节。
  • Host 的编码并不是简略的把 IP:Port 作为字符串间接转化为 byte 数组,而是每个数字当作 byte 顺次编码。在下一节的 Golang 代码中会阐明。
  • 扩大信息的编码中,应用了不可见字符作为宰割,所以扩大字段 key-value 中不能蕴含那 2 个不可见字符。具体是哪 2 个,大家找找?

咱们看到这个协定后,如何证实你的物理文件就是依照这个协定写的呢?

4 用 Golang 解开 RocketMQ Commit Log

RocketMQ 是用 java 写的,依据上文形容的存储协定,我用 Golang 编写了一个工具,能够解开 Commit Log 和 Cosumer Queue,代码地址:

https://github.com/rmq-plus-plus/rocketmq-decoder

这个工具目前反对 2 个性能:

  • 指定 Commit Log 位点,间接解析 Commit Log 中的音讯,并且打印。
  • 指定生产位点,先解析 Consumer Queue,失去 Commit Log Offset 后,再依据 Commit Log Offset 间接解析 Commit Log,并且打印。

在 Golang 中没有依赖 RocketMQ 的任何代码,纯正是依附协定解码。

golang-import

这里贴了一段 golang 中解析 Commit Log Offset 的例子:在 java 中这个 offset 是一个 long 类型,占用 8 个字节。

在 golang 中,读取 8 个字节长度的数据,并且依照大端序解码为 int64,就能够失去失常的 Commit Log Offset。

Golang-demo

我跑了一个 demo 后果,大家参考:

读取 consumer-queue-commit-log

5 答复最后的问题

以下为个人见解,大家参考:

1.1 Consumer Queue Offset 是间断的吗,为什么?

是间断的。

consumer queue offset,是指每个 queue 中索引音讯的下标,下标当然是间断的。消费者也是利用了这个连续性,防止生产位点提交空洞的。

每个索引音讯占用雷同空间,都是 20 字节,构造如下:

consumer-queue 索引音讯构造

这里物理位点也就是 Commit Log Offset。

1.2 Commit Log Offset 是间断的吗,为什么?

不是间断的。

Commit Log Offset 是指的每个音讯在全副 Commit Log 文件中的字节偏移量,每个音讯的大小是不确定的,所以 Commit Log Offset,也即是字节偏移量必定是不一样的。

并且能够晓得,每两个偏移量的差的绝对值就是前一个音讯的音讯字节数总长度。

并且上文中图“Commit Log 存储文件散布形象”中的有误会,每个小方格的大小其实是不一样的。

1.3 Java 写的文件,默认是大端序还是小端序,为什么?

大端序。字节序其实有数据存储程序和网络传输程序两种,java 中默认用的大端序,放弃和网络传输一样,这样不便编解码。

每段网络传输层的数据报文最后面的字节是表白前面的数据是用什么协定传输的,这样数据接收者在承受数据时,依照字节程序,先解析协定,再依据协定解码前面的字节序列,合乎人类思考和解决问题的形式。

以上是我的了解,有任何问题,能够进社区群细聊。

探讨阐明:因为 RocketMQ 一些版本可能有差别,本文在 4.9.3 版本下探讨。

退出移动版