深入 RocketMQ 存储与原理

📅 2026/7/19 4:16:17
深入 RocketMQ 存储与原理
消息存储机制CommitLog 的设计与原理我们先从最核心的 CommitLog 说起。CommitLog 是什么简单说CommitLog 是 RocketMQ 存储消息的“主文件”。每个 Broker 上只有一个 CommitLog 文件严格说是有一组按大小滚动的文件所有 Topic 的所有消息都顺序写入到这个文件里。你可以把 CommitLog 想象成一本巨大的流水账本——不管是谁的消息来了就按顺序往本子上记先来先记后来后记绝不跳着写。为什么这么设计这里有个计算机存储的核心知识点磁盘顺序写入的速度比随机写入快几十甚至上百倍。为什么因为机械硬盘的读写依赖于磁头移动随机写入意味着磁头要到处“跳”每次跳跃都需要寻道时间平均约 5-10ms。而顺序写入磁头几乎不需要移动数据像流水一样连续不断地写到磁盘上。即使是 SSD顺序写入也能更好地利用带宽减少写放大效应。RocketMQ 正是利用了这个特性让所有消息都顺序追加到 CommitLog从而获得了极高的写入吞吐量。CommitLog 的文件结构CommitLog 在磁盘上是一个文件集合每个文件默认 1GB文件名就是起始偏移量用 20 位数字表示不足补 0~/store/commitlog/├── 00000000000000000000 (第1个文件偏移量 0 开始)├── 00000000001073741824 (第2个文件偏移量 1GB 开始)├── 00000000002147483648 (第3个文件偏移量 2GB 开始)└── …每个消息在 CommitLog 中的存储格式如下字段 长度 说明消息总长度 4 字节 整个消息条目的字节数消息序号 8 字节 消息的唯一递增序号存储时间戳 8 字节 消息存储时的时间戳消息体长度 4 字节 消息体的字节数消息体 变长 实际的消息内容扩展属性 变长 Topic、Tag、Key 等属性… … 其他元数据这样的设计意味着写入 CommitLog 完全不区分 Topic所有消息一视同仁顺序追加——这也是 RocketMQ 写入性能极高的根本原因。ConsumeQueue 的设计与原理如果 CommitLog 是所有消息的大杂烩那消费者怎么快速找到自己想要的消息呢这就是 ConsumeQueue 的用武之地。ConsumeQueue 是什么ConsumeQueue 是消息的“索引文件”每个 MessageQueue 对应一个 ConsumeQueue 文件。如果说 CommitLog 是“流水账本”那 ConsumeQueue 就是“分类目录”——它不存储消息本身只存储每条消息在 CommitLog 中的物理位置偏移量以及消息的大小和 Tag 的哈希值。ConsumeQueue 的存储格式每个 ConsumeQueue 条目固定 20 个字节非常轻量字段 长度 说明CommitLog 偏移量 8 字节 消息在 CommitLog 中的物理位置消息长度 4 字节 消息的字节数Tag 哈希码 8 字节 Tag 的哈希值用于消息过滤这意味着即使 CommitLog 有几十 GBConsumeQueue 也只有它的几十分之一大小可以轻松加载到内存中消费者查找消息时几乎不会产生磁盘 I/O。 小贴士这就是 RocketMQ 的“空间换时间”策略——用一个轻量的索引文件让消息查找从 O(n) 变成了 O(1)。CommitLog 与 ConsumeQueue 的协作关系CommitLog 和 ConsumeQueue 是怎么配合工作的下面这张图展示了它们之间的完整协作关系消费端存储层生产端发送消息顺序写入消息写入成功返回偏移量异步构建索引提取消息信息提取消息信息提取消息信息根据 Queue 和偏移量返回 CommitLog 偏移量根据物理偏移量精准读取返回消息体ProducerBrokerCommitLog单一文件所有消息共用ReputMessageService后台线程ConsumeQueueTopicA-Queue0ConsumeQueueTopicA-Queue1ConsumeQueueTopicB-Queue0Consumer整个协作流程分为写入链路和消费链路两条线写入链路图中的 1→2→3→4Producer 发送消息到 BrokerBroker 将消息顺序写入 CommitLog这一步就返回 ACK 给 Producer后台线程 ReputMessageService 异步地从 CommitLog 中解析消息根据消息所属的 Topic 和 Queue将索引信息写入对应的 ConsumeQueue消费链路图中的 5→6→7→85. Consumer 根据自己的消费进度Queue 偏移量查询 ConsumeQueue6. ConsumeQueue 返回消息在 CommitLog 中的物理偏移量7. Consumer 根据物理偏移量直接从 CommitLog 读取消息内容8. CommitLog 返回完整的消息体注意写入和构建索引是异步解耦的——消息一旦写入 CommitLog 就返回成功索引的构建在后台慢慢追。这就是 RocketMQ 写入延迟极低的原因之一。消息写入 CommitLog 的完整流程一条消息从 Producer 发出到最终落盘到底经历了哪些步骤我们用一张详细的流程图来还原是否同步刷盘异步刷盘Producer 发送消息消息到达 Broker消息合法性校验Topic 是否存在 / 消息体大小是否超限消息内容准备生成消息 ID / 时间戳 / 计算 CRC是否配置了消息轨迹记录消息轨迹数据获取当前 CommitLog 文件的写入位置全局锁将消息按固定格式序列化写入 CommitLog 的追加位置更新 CommitLog 的写入指针位置刷盘策略强制将数据从 PageCache刷入物理磁盘返回写入结果给 Producer数据仅在 PageCache 中等待后台线程异步刷盘后台线程 ReputMessageService异步构建 ConsumeQueue 索引消费者后续可消费到该消息步骤解析消息到达 BrokerProducer 通过网络将消息发送到 Broker 的指定端口合法性校验检查 Topic 是否存在、消息体是否超过 4MB默认限制等消息内容准备生成全局唯一的消息 ID记录到达时间戳计算 CRC 校验码消息轨迹记录可选如果开启了消息轨迹功能会记录消息的发送链路信息获取写入位置通过全局锁putMessageLock获取当前 CommitLog 文件的写入偏移量——注意这里是加锁的但因为是顺序写锁的持有时间极短不影响并发序列化写入将消息按照固定格式魔数、消息体大小、消息体、扩展属性等序列化为字节数组追加到 CommitLog 文件末尾更新指针更新内存中的写入位置指针为下一条消息做准备刷盘根据配置的刷盘策略决定是立即刷盘还是只写到操作系统缓存PageCache返回结果将写入状态成功/失败和消息 ID 返回给 Producer异步构建索引后台线程异步构建 ConsumeQueue 索引不影响主流程的响应速度ConsumeQueue 的异步构建机制ReputMessageService上面多次提到了 ReputMessageService这是 RocketMQ 中一个非常关键的后台服务。我们来深入了解一下它到底是怎么工作的。ReputMessageService 是什么它是 RocketMQ 中负责异步构建 ConsumeQueue 索引的后台线程服务。它像一个勤劳的“搬运工”不断从 CommitLog 中“搬运”消息的索引信息到对应的 ConsumeQueue 中。为什么需要异步构建如果每写入一条消息就同步去更新 ConsumeQueue会有两个问题增加了写入链路的延迟本来只需要写一次 CommitLog现在要写两次无法保证 ConsumeQueue 的写入也是顺序的不同 Topic/Queue 是分散的异步构建意味着写入 CommitLog 就立即返回成功索引在后台慢慢构建。ReputMessageService 的工作流程IndexFileConsumeQueueReputMessageServiceCommitLogProducerIndexFileConsumeQueueReputMessageServiceCommitLogProducer推动 CommitLog 的消费进度指针 (reputFromOffset)alt[有新数据][无新数据]loop[每毫秒轮询]发送消息写入 CommitLog返回写入成功不等待索引检查 CommitLog 是否有新数据解析新消息的物理位置和属性计算 ConsumeQueue 偏移量写入索引条目如果配置了 IndexFile构建哈希索引短暂休眠等待下一轮关键点ReputMessageService 会记录一个 reputFromOffset 指针标记当前已构建到 CommitLog 的哪个位置每次轮询时从该位置读取新数据构建索引然后更新指针如果某条消息的 Topic 或 Queue 不存在ReputMessageService 会跳过并继续同时记录错误日志这种设计保证了即便索引构建失败原始消息依然安全地存储在 CommitLog 中数据不会丢失消息的索引文件IndexFile的作用除了 ConsumeQueue 这个“主索引”RocketMQ 还有一个 IndexFile索引文件用于支持根据消息 Key 查询消息。IndexFile 是什么IndexFile 是一个基于哈希索引的查询文件用于快速定位消息在 CommitLog 中的位置。IndexFile 的结构索引条目结构每条 20 字节Key 的哈希值4 字节CommitLog 偏移量8 字节消息存储时间差4 字节同哈希槽的下一条索引4 字节IndexFile 文件结构文件头部创建时间、消息总数、哈希槽数量等哈希槽数组默认 500 万个槽位索引条目数组默认 2000 万条Key 查询流程Producer 发送消息时指定 Key例如订单 IDBroker 在构建索引时对 Key 进行哈希运算存入 IndexFile用户通过控制台或 API 查询时输入 Key 值Broker 对 Key 进行同样的哈希运算在 IndexFile 中找到对应的消息位置再通过 CommitLog 偏移量读取完整消息 小贴士ConsumeQueue 和 IndexFile 的区别在于——ConsumeQueue 用于按队列顺序消费IndexFile 用于按业务 Key 精确查询。一个是“扫货架”一个是“查字典”。