重新理解 RocketMQ Commit Log 存储协议

2023-08-01 11:35:01

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

问题思考

1、Consumer Queue Offset 是连续的吗, 为什么?

2、Commit Log Offset 是连续的吗, 为什么?

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

Commit Log 真实分布

在大家思考之际, 我们回想下 commit log 是怎么分布的呢?

在 Broker 配置的存储根目录下,通过查看 Broker 实际生成的 commit log 文件可以看到类似下面的数据文件分布:

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

我们结合源码可以知道,实际的抽象模型如下:

由上图得知:

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

我们看到 Commit Log 文件中有很多个消息,按照既定的协议存储的,那具体协议是什么呢, 你是怎么知道的呢?

Commit Log 存储协议

关于 Commit Log 存储协议,我们问了下 ChatGPT, 它是这么回复我的,虽然不对,但是这个回复格式和说明已经非常接近答案了。

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

我整理后, 如下图:

说明 1:我整理后的消息协议编号和代码中不是一致的,代码中只是标明了顺序, 真实物理文件中的存储协议会更详细。说明 2:在我写的《RocketMQ 分布式消息中间件:核心原理与最佳实践》中,这个图缺少了 Body 内容,这里加了,也更详细的补充了其他数据。 这里有几个问题需要说明下:

1、二进制协议存在字节序,也就是常说的大端、小端。大小端这里不详细说明感兴趣的同学自己 google 或者问题 ChatGPT,回答肯定比我说的好。

2、在 java 中, 一个 byte 占用 1 个字节,1 个 int 占用 4 个字节,1 个 short 占用 2 个字节,1 个 long 占用 8 个字节。

3、Host 的编码并不是简单的把 IP:Port 作为字符串直接转化为 byte 数组,而是每个数字当作 byte 依次编码。在下一节的 Golang 代码中会说明。

4、扩展信息的编码中,使用了不可见字符作为分割,所以扩展字段 key-value 中不能包含那 2 个不可见字符。具体是哪 2 个,大家找找?

我们看到这个协议后,如何证明你的物理文件就是按照这个协议写的呢?

用 Golang 解开 RocketMQ Commit Log

RocketMQ 是用 java 写的,根据上文描述的存储协议,我用 Golang 编写了一个工具,可以解开 Commit Log 和 Cosumer Queue,代码地址:https://github.com/rmq-plus-plus/rocketmq-decoder。

这个工具目前支持 2 个功能:

1、指定 Commit Log 位点,直接解析 Commit Log 中的消息,并且打印。

2、指定消费位点,先解析 Consumer Queue,得到 Commit Log Offset 后,再根据 Commit Log Offset 直接解析 Commit Log,并且打印。

在 Golang 中没有依赖 RocketMQ 的任何代码,纯粹是依靠协议解码。

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

在 golang 中,读取 8 个字节长度的数据,并且按照大端序解码为 int64,就可以得到正常的 Commit Log Offset。

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

回答最初的问题

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

1、Consumer Queue Offset 是连续的吗, 为什么?

是连续的。

consumer queue offset,是指每个 queue 中索引消息的下标,下标当然是连续的。消费者也是利用了这个连续性,避免消费位点提交空洞的。

每个索引消息占用相同空间,都是 20 字节,结构如下:

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

2、Commit Log Offset 是连续的吗, 为什么?

不是连续的。

Commit Log Offset 是指的每个消息在全部 Commit Log 文件中的字节偏移量, 每个消息的大小是不确定的,所以 Commit Log Offset,也即是字节偏移量肯定是不一样的。

并且可以知道,每两个偏移量的差的绝对值就是前一个消息的消息字节数总长度。

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

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

大端序。大端序其实有字节存储顺序和网络传输顺序,java 中默认用的大端序,保持和网络传输一样,这样方便编解码。

每段网络传输层的数据报文最前面的字节是表达后面的数据是用什么协议传输的,这样数据接收者在接受数据时, 按照字节顺序,先解析协议,再根据协议解码后面的字节序列,符合人类思考和解决问题的方式。

讨论说明:由于 RocketMQ 一些版本可能有差异,本文在 4.9.3 版本下讨论,大家可以参考这个方法,解开 5.0 甚至其他版本,其他数据文件的存储协议格式。

更多推荐

恒合仓库 - 用户管理、用户列表、为用户分配角色

文章目录用户管理一、用户列表1.1实体类1.1.1分页实体类1.1.2用户信息实体类1.2业务实现1.2.1UserMapper1.2.2Service层1.2.3Controller层1.2.4效果图二、用户增删改查2.1添加用户业务实现2.1.1Mapper2.1.2Service2.1.3Controller2.

BUU [HCTF 2018]Hideandseek

BUU[HCTF2018]Hideandseek考点:软连接读取任意文件Flask伪造session/proc/self/environ文件获取当前进程的环境变量列表random.seed()生成的伪随机数种子MAC地址(存放在/sys/class/net/eth0/address文件)国赛的时候遇见过软连接,这次再来

Redis缓存

目录什么是缓存?缓存特性1、缓存雪崩2、缓存穿透3、缓存击穿4、缓存预热什么是缓存?在程序中如果没有设置缓存的时候,用户想要获取到数据一般都是直接从数据库中获取。加入缓存之后会这样执行我们都知道查询数据库是一个比较慢的过程,对用户而言这样的体验是非常不好的。加入缓存之后,查询数据就会先在缓存中查找,如果缓存中没有才会去

C++项目实战——基于多设计模式下的同步&异步日志系统-⑨-同步日志器类与日志器建造者类设计

文章目录专栏导读Logger类设计同步日志器类设计同步日志器测试日志器建造者模式设计抽象日志器建造者类派生局部日志器建造者日志器建造者类测试同步日志器类与日志器建造者类整理专栏导读🌸作者简介:花想云,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于C/C++、Linu

电力系统直流潮流分析【N-1】(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。⛳️座右铭:行百里者,半于九十。📋📋📋本文目录如下:🎁🎁🎁目录💥1概述📚2运行结果🎉3参考文献🌈4Matlab代码及文档讲解💥1概述该程序接受一个感受矩阵B=[NxN]和注入功

从零开始:使用Python创建GUI驱动的简易国际象棋游戏

第一部分:国际象棋的基础1.介绍国际象棋,一个古老而又充满策略的游戏,历经数世纪的发展,至今仍然广受喜爱。那么,如何使用Python来创建一个简单的国际象棋游戏,并给它加上一个图形界面(GUI)呢?这篇文章将指导您一步步完成这一目标。2.定义棋盘和棋子首先,我们需要定义国际象棋的棋盘和棋子。棋盘是一个8x8的方格,通常

【备考网络工程师】如何备考2023年网络工程师之错题集篇(1)

文章目录写在前面涉及知识点一、自我认知(做一套真题)二、错题整理总结写在前面上半年试了一下软考的水,没想到居然过了,当然当时考的是初级-网络管理员,但是我觉得应该追求再高一些,正好比王勃说的穷且益坚,不坠青云之志。所以我得时刻保持自己学习的状态,为遇见明天更好的自己而加油,因此我也趁备战之初记录下来,有一起备考的赶紧加

巨人互动|Facebook海外户&Facebook有什么功能

Facebook是一款国际化的用于聊天的软件,Facebook一般情况下用户可以在其共享照片、发布评论以及在网络上发布新闻或者其他有趣内容的链接,观看短视频或者实时聊天等。那么Facebook也拥有广泛的功能和特点。巨人互动|Google海外户&Google内容定位介绍(◀想要了解更多可点击查看)巨人互动|Google

安全保护策略:iOS应用程序代码保护的关键步骤和技巧

​转载:怎么保护苹果手机移动应用程序iosipa文件中的代码?目录转载:怎么保护苹果手机移动应用程序iosipa文件中的代码?代码混淆步骤1.选择要混淆保护的ipa文件2.选择要混淆的类名称3.选择要混淆保护的函数,方法4.配置签名证书5.混淆和测试运行​编辑在当今移动应用市场竞争激烈的环境中,代码保护功能对于iOS应

异地访问Oracle数据库的解决方案:利用内网穿透实现PL/SQL远程连接的建议与步骤

文章目录前言1.数据库搭建2.内网穿透2.1安装cpolar内网穿透2.2创建隧道映射3.公网远程访问4.配置固定TCP端口地址4.1保留一个固定的公网TCP端口地址4.2配置固定公网TCP端口地址4.3测试使用固定TCP端口地址远程Oracle​小月糖糖主页在强者的眼中,没有最好,只有更好。移动开发领域优质创作者,阿

深入实现 MyBatis 底层机制的任务阶段3 - 封装 SqlSession 到执行器

😀前言在前面的几个任务阶段中,我们已经深入研究了如何自己实现MyBatis底层机制的关键组件,包括配置文件的读取、数据库连接的建立、执行器的编写以及SQL查询操作的实现。这些步骤为我们构建自定义MyBatis底层框架奠定了坚实的基础。.在本文中,我们将继续前进,进入任务阶段3,这一阶段的关键任务是将SqlSessio

热文推荐