RocketMQ引入了ConsumeQueue,每个消息主题包括多个消息消费队列,每个消息队列有一个消息文件
Broker 接收到生产者发送消息请求后如何存储在 Broker 上
核心实现类DefaultMessageStore
先看核心方法putMessage
1.校验不能是slave,只允许写master
2.校验message store是可写的
3.校验mq的topic长度不能超过最大限制256
4.校验mq属性不能过长
5.检测操作系统页写入是否繁忙
6.将日志写入CommitLog 文件,具体实现类 CommitLog
6.1 需要加锁putMessageLock
6.2 设置消息存储时间
6.3 把消息追加到mappedFile:先获取mappedFile的位移createOffset,然后根据写入数据大小构建出mappedFileLast
然后调用appendMessage 追加到文件;
this.wrotePosition.addAndGet(result.getWroteBytes()); 写完后更新wrotePosition
7.创建消息id
8.记录消费队列consumeQueue 信息
9.把message序列化成为字节数组
10.检查当前消息长度是否超过最大限制
11.写入到msgStoreItemMemory,也就是把消息存入到内存中
12. 重点讲解一下AppendMessageResult方法。
消息刷盘的实现,分成同步刷盘,异步刷盘
1.同步刷盘实现
1.1 构建一个GroupCommitRequest
1.2 等待同步刷盘任务完成,如果超时,则返回刷盘错误;正常就返回给调用方
从上面的代码看,我们可以把 doCommit 方法当成业务方法,在 run 方法的循环被调用,每执行完一次 doCommit 等待10毫秒,这也是 waitForRunning 的核心逻辑,doCommit 中的任务是通过调用如下方法:
10ms执行一次提交doCommit
刷盘具体实现:MappedFileQueue。
1. 根据上次刷新的位置,得到当前的 MappedFile 对象。
2. 执行 MappedFile 的 flush 方法。
3.更新上次刷新的位置。
刷写的实现逻辑就是调用 FileChannel 或 MappedByteBuffer 的force 方法
根据上次刷盘偏移量,找到当前待刷盘mappedFile对象
request.waitForFlush, 类似于 Future 模式,在这方法里进行阻塞等待。
1)消息追加,也就是将消息追加到 CommitLog 文件对应的内存映射区(本过程是加锁的,非并发;2)刷盘阶段(并发)就是将内存区数据刷写到磁盘文件(支持同步、异步刷盘);3)主从同步处理(并发)。
+++++++++++++++++++++++++++++++++++++++++++++++++
RocketMQ的刷盘机制就介绍到这,我们再简单做个总结。
先讲一下 RocketMQ 的存储设计亮点:(以CommitLog为例)。
单个 commitlog 文件,默认大小为 1G,由多个 commitlog 文件来存储所有的消息,commitlog 文件的命名以该文件在整个commitlog中的偏移量来命名,举例如下。
例如一个 commitlog 文件,1024个字节。
第一个文件: 00000000000000000000
第二个文件: 00000000000000001024
MappedFile 封装一个一个的 CommitLog 文件,而 MappedFileQueue 就是封装的就是一个逻辑的 commitlog 文件。mappedFile队列,从小到大排列。
使用内存映射机制,MappedByteBuffer, 具体封装类为MappedFile。
1、同步刷盘每次发送消息,消息都直接存储在 MapFile 的 mappdByteBuffer,然后直接调用 force() 方法刷写到磁盘,等到 force 刷盘成功后,再返回给调用方(GroupCommitRequest#waitForFlush)就是其同步调用的实现。
2、异步刷盘
分为两种情况,是否开启堆外内存缓存池,具体配置参数:MessageStoreConfig#transientStorePoolEnable。
1)transientStorePoolEnable = true
消息在追加时,先放入到 writeBuffer 中,然后定时 commit 到 FileChannel,然后定时flush。
2)transientStorePoolEnable=false(默认),不开启堆外内存,就存入到MappedByteBuffer
消息追加时,直接存入 MappedByteBuffer(pageCache) 中,然后定时 flush。
+++++++++++++++++++++++++++++++++++++++++++++++++
存储文件组织与内存映射
RocketMQ使用MappedFile, MappedFileQueue来封装存储文件
1.根据消息存储时间戳查找MappedFile :
从MappedFile列表中第一个文件开始查找,找到第一个最后一次更新时间戳大于待查找时间戳的文件。不存在就返回最后一个MappedFile文件
2.根据消息偏移量offset查找MappedFile :
4.5.2 ConsumeQueue
1.提供根据消息存储时间 查找具体实现的方法 getOffsetInQueueByTime
根据时间戳定位到物理文件,然后根据二分查找来加速检索