本文作者:李伟,社区里大家叫小伟,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版本下探讨。