RocketMQ引入了ConsumeQueue,每个消息主题包括多个消息消费队列,每个消息队列有一个消息文件 

RocketMQ 源码分析 05 消息存储_时间戳

RocketMQ 源码分析 05 消息存储_封装_02

Broker 接收到生产者发送消息请求后如何存储在 Broker 上

 核心实现类DefaultMessageStore

先看核心方法putMessage

RocketMQ 源码分析 05 消息存储_时间戳_03

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

RocketMQ 源码分析 05 消息存储_内存映射_04

8.记录消费队列consumeQueue 信息

9.把message序列化成为字节数组

10.检查当前消息长度是否超过最大限制

RocketMQ 源码分析 05 消息存储_封装_05

11.写入到msgStoreItemMemory,也就是把消息存入到内存中

12. 重点讲解一下AppendMessageResult方法。

RocketMQ 源码分析 05 消息存储_时间戳_06

消息刷盘的实现,分成同步刷盘,异步刷盘

RocketMQ 源码分析 05 消息存储_封装_07

1.同步刷盘实现

  1.1 构建一个GroupCommitRequest

  1.2 等待同步刷盘任务完成,如果超时,则返回刷盘错误;正常就返回给调用方

RocketMQ 源码分析 05 消息存储_内存映射_08

从上面的代码看,我们可以把 doCommit 方法当成业务方法,在 run 方法的循环被调用,每执行完一次 doCommit 等待10毫秒,这也是 waitForRunning 的核心逻辑,doCommit 中的任务是通过调用如下方法:

10ms执行一次提交doCommit

刷盘具体实现:MappedFileQueue。

1. 根据上次刷新的位置,得到当前的 MappedFile 对象。

2. 执行 MappedFile 的 flush 方法。

3.更新上次刷新的位置。

刷写的实现逻辑就是调用 FileChannel 或 MappedByteBuffer 的force 方法

RocketMQ 源码分析 05 消息存储_内存映射_09

根据上次刷盘偏移量,找到当前待刷盘mappedFile对象

 request.waitForFlush, 类似于 Future 模式,在这方法里进行阻塞等待。

RocketMQ 源码分析 05 消息存储_时间戳_10

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 :

RocketMQ 源码分析 05 消息存储_时间戳_11

RocketMQ 源码分析 05 消息存储_时间戳_12

RocketMQ 源码分析 05 消息存储_封装_13

RocketMQ 源码分析 05 消息存储_时间戳_14

RocketMQ 源码分析 05 消息存储_时间戳_15

RocketMQ 源码分析 05 消息存储_时间戳_16

4.5.2 ConsumeQueue

RocketMQ 源码分析 05 消息存储_内存映射_17

1.提供根据消息存储时间 查找具体实现的方法 getOffsetInQueueByTime

   根据时间戳定位到物理文件,然后根据二分查找来加速检索

RocketMQ 源码分析 05 消息存储_封装_18

RocketMQ 源码分析 05 消息存储_内存映射_19

RocketMQ 源码分析 05 消息存储_内存映射_20

RocketMQ 源码分析 05 消息存储_封装_21