重新理解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回复
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版本下讨论。
重新理解RocketMQ Commit Log存储协议的更多相关文章
- 从源码分析RocketMq消息的存储原理
rocketmq在存储消息的时候,最终是通过mmap映射成磁盘文件进行存储的,本文就消息的存储流程作一个整理.源码版本是4.9.2 主要的存储组件有如下4个: CommitLog:存储的业务层,接收& ...
- 从code review到Git commit log
最近在读一本技术类的书:朱赟——<跃迁:从技术到管理的硅谷路径>,其中聊了很多很有趣的观点,比如:技术管理.技术实践.硅谷文化.个人成长等. 读到关于硅谷人如何做code review这一 ...
- 1.2 Use Cases中 Commit Log官网剖析(博主推荐)
不多说,直接上干货! 一切来源于官网 http://kafka.apache.org/documentation/ Commit Log 提交日志 Kafka can serve as a kind ...
- 深入理解计算机系统cp1:存储单位与编码
摘要: 理解计算机是如何存储数据的. 原文:深入理解计算机系统cp1:存储单位与编码 作者:Chor Fundebug经授权转载,版权归原作者所有. 1. 存储单位 位:即 bit,表示二进制位,要么 ...
- 深入理解RocketMQ的消费者组、队列、Broker,Topic
1.遇到的问题:上测试环境,上次描述的鸟问题又出现了,就是生产者发3条数据,我这边只能收到1条数据. 2.问题解决: (1)去控制台看我的消费者启动情况,貌似没什么问题 , (2)去测试服务器里看日志 ...
- 遍历仓库里的 commit log 替换author
#!/bin/sh # 遍历仓库里的 commit log, 替换author git filter-branch --env-filter ' an="$GIT_AUTHOR_NAME&q ...
- Kafka深入理解-2:Kafka的Log存储解析
摘自http://blog.csdn.net/jewes/article/details/42970799 引言 Kafka中的Message是以topic为基本单位组织的,不同的topic之间是相互 ...
- kafka的log存储解析——topic的分区partition分段segment以及索引等
转自:http://blog.csdn.net/jewes/article/details/42970799 引言 Kafka中的Message是以topic为基本单位组织的,不同的topic之间是相 ...
- Caffe源码理解1:Blob存储结构与设计
博客:blog.shinelee.me | 博客园 | CSDN Blob作用 据Caffe官方描述: A Blob is a wrapper over the actual data being p ...
- kafka的log存储解析——topic的分区partition分段segment以及索引等(转发)
原文 https://www.cnblogs.com/dorothychai/p/6181058.html 引言 Kafka中的Message是以topic为基本单位组织的,不同的topic之间是相互 ...
随机推荐
- Ubuntu-18.04.1-live-server-amd64.iso安装全过程
以下向你介绍安装Ubuntu-18.04.1-live-server-amd64.iso版本的全过程,同时要说明的是Ubuntu 18.04.1 server版本只能通过文本的方式安装,即它没有和Ub ...
- HTML5第五章作业
5.1.3 html 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" ...
- Docker In Action 学习笔记 根据第二版有所更新
第一章 最简单的 hello_world/Dockerfile FROM busybox:latest CMD ["echo", "hello world"] ...
- logging 模块详解
日志记录函数以它们用来跟踪的事件的级别或严重性命名.下面描述了标准级别及其适用性(从高到低的顺序) 日志等级(level) 描述DEBUG 最详细的日志信息,典型应用场景是 问题诊断INFO 信息详细 ...
- 关于 layui 弹出一个 DOM 表单的问题
案例: 假设用 layer.msg 去弹出一个dom表单: 由官方文档可知,应该定义一个div,设置其 id 为某个值,然后写在 content 中: layer弹层组件开发文档 - Layui 但 ...
- ASP.NET实现前台调用后台变量或者方法
前台页面 <div> <%= Name %> </div> <div> <%= getName() %> </div> 后台代码 ...
- 使用 netstat 命令监视网络状态
在linux 系统网络出现问题时可以使用netstat -s 来分析问题 使用 netstat 命令监视网络状态 netstat 命令生成包含网络状态和协议统计信息的显示内容.可以通过表格形式显示 T ...
- 后端008_配置Security登录授权过滤器
------------恢复内容开始------------ 现在我们就可以去进行springscurity的配置了.首先我们新建一个配置类.然后该类需要添加@Configuration注解,然后还要 ...
- Nginx TP框架伪静态配置
location / { if (!-e $request_filename){ rewrite ^(.*)$ /index.php?s=$1 last; break; } }
- gerrit部署使用
前言 gerrit是基于git的工具,code review功能是开发中很方便,此外它可以配合Jenkins进行自动化的编译工作.今天给大家分享一下gerrit在ubuntu的安装使用 作者:良知犹存 ...