直观感受一下RocketMQ的存储磁盘文件
首先磁盘文件存储不是在安装目录,默认是在启动进程的用户目录下的,比如root用户就是/root/store目录。然后里边有commitlog,config,consumequeue,index这几个子目录,以及abort,checkpoint,lock这几个文件。
- config目录里边的东西看起来应该是topic、订阅组、消费offset之类的配置信息。
-
index目录里边是一个(若干个)几百M左右的hash索引文件,跟数据库类似、方便快速查找用的,文件名跟安装时候的日期时间一样。例如: /root/store/index/20210323163210194
主要是建立一种通过消息的key属性快速找到消息所在位置offset的机制。
每个索引文件由3个主要部分组成:IndexHead、500万个Hash Slot、2000万个索引条目。- IndexHead里边放当前索引文件的索引的消息的最大和最小时间,最大与最小offset、本文件中已使用了多少索引条目。
- 相同hashcode的key的commitLog offset用链表存起来、这些链表共同组成了索引条目。Hash槽则负责记录每个链表当前最后一个节点所在的位置(在索引文件里的offset)。
- 这个索引是怎么用的?
比如根据key要找一个消息,先计算key的hashcode,根据这个hashcode到hash slot里边找到对应的链表的最后一个节点在本索引文件中的位置,
找到对应的链表的最后一个节点,由此节点开始遍历这个链表直到找到这个key和消息对应的commitLog offset、也就是物理位置。
-
commitlog里边是若干个1G大小的磁盘文件。
类似这样的: /root/store/commitlog/00000000000000000000
消息就是存在commitLog文件里的,里边按顺序的存储每个消息的长度等一些元数据和消息body数据。commitLog可以说是RocketMQ这个系统的核心数据结构。 -
consumequeue目录里边又按照每个topic做了分类建了目录,打开一个topic的目录会发现0、1、2、3四个目录,代表4个consume queue,每个queue目录里边是若干个消费队列文件,每个6M大小。
不同topic的消息实际上是混着存放在commitLog文件里的,如果要按topic找消息有所不变。而consumequeue消费队列文件就是解决这个问题的。
每个消息输出到CommitLog的同时,把他们的3个关键信息字段作为1个条目输出到这个topic对应的几个consumequeue文件中去。
每个consumequeue中有30万个条目,每个条目有共20字节的3个字段:该消息的commitLog offset、消息大小、消息Tag的hashcode。 -
abort 关闭异常文件
RocketMQ出现了异常关闭就会往这个文件写东西 -
checkpoint 检测点文件
里边24Byte存了三个字段,commitLog刷盘的时间点、消费队列文件刷盘的时间点、索引文件刷盘时间点
参考:《RocketMQ存储文件》 https://my.oschina.net/mingxungu/blog/3083961 (详细好文)
高性能的秘密
kernel的mmap内存映射技术,提供了一种能力:可以将物理磁盘文件映射到虚拟内存、形成PageCache。用户程序对PageCache进行读写、之后由操作系统异步的将数据从PageCache刷入磁盘或从磁盘将数据加载到PageCache进行缓存。这样大大提高了用户程序读写磁盘文件的性能。
上面的os层能力通过java平台来提供就是:通过JDK NIO的MappedByteBuffer.map()将磁盘与vm进行映射,之后对MappedByteBuffer进行读写,os会通过异步线程完成读缓存和异步写同步的工作。
参考:《RocketMQ 如何基于mmap+page cache实现磁盘文件的高性能读写》 http://www.imooc.com/article/301624 (高性能还是要靠kernel的能力啊)
普通CommitLog与DLedgerCommitLog
RocketMQ源码中可以看到,在实例化DefaultMessageStore的时候:
if (messageStoreConfig.isEnableDLegerCommitLog()) {
this.commitLog = new DLedgerCommitLog(this);
} else {
this.commitLog = new CommitLog(this);
}
上面的CommitLog跟DLedgerCommitLog的区别在于,
rocketMQ原来的m-s机制只实现了主从复制、确保了数据不丢失、以及客户端与服务端不发生不一致问题。但是没有实现高可用:即自动的选举master并切换。可以跟redis的m-s、哨兵方案做下类比进行理解。
DLedgerCommitLog使用raft协议做了选举机制,可以自动选举并切换master。
参考:《DLedger —基于 raft 协议的 commitlog 存储库》https://developer.aliyun.com/article/713017
阿里数据一致性实践:Dledger 技术在消息领域的探索和应用-InfoQ
消费消息,到底是拉取还是推送?
RocketMQ的Pull和Push两种消费方式,Pull底层实现方式是最一般的短轮询、也就是一般理解上的“客户端不断向服务端轮询、主动拉取数据”。但是Push则跟我们一般意义理解上的“推送”有些不同(例如websocket推送),RocketMQ采用的Push底层实际上是以长轮询方式实现的,本质上还是属于拉取:客户端请求服务端,服务端有数据则立刻返回数据,没有数据则先持有这个socket连接先不返回、之后不断通过自己内部的机制确认数据是否生成好了,准备好了之后在把数据通过持有的socket连接返回给客户端。客户端收到返回数据之后关闭连接,之后再次发起下一次轮询请求。
与普通的短轮询相比,长轮询做了优化,没有数据的时候不会立刻返回告知客户端没有数据、这会让客户端产生很多无用轮询,而是保持连接等待服务端有数据再返回、这样客户端发起请求的频率不用像普通轮询方式那样频繁、减少很多无用请求。
同时,由于长轮询本质上还是客户端主动请求的、而不是像websocket那样、客户端是被动接收服务端推送来的数据,所以这样的一个好处是客户端不用担心被服务端的推送流量压垮,而是按照自己的处理节奏来主动向服务端发起轮询。这从RocketMQ的定位来看非常重要:本身充当的是生产与消费两端中间的缓冲、要利用好自己本身是文件系统的吞吐能力当好这个缓冲而不是把下游的消费端给压死。
参考:《从RocketMQ看长轮询Long Polling》 https://segmentfault.com/a/1190000018411470
网友评论