RocketMQ的消息存储结构 RocketMQ的消息存储结构背景学习RocketMQ文章目录前置知识一. RocketMQ 消息存储架构CommitLog物理文件的“大熔炉”ConsumeQueue消费逻辑队列 / 小账本IndexFile哈希索引文件 / 业务查日志神器二. 图中关键细节三. 从生产者-存储-消费者完整流程前置知识有关MQ的简要流程producer通过MQ发送消息MQ将消息保存下来并维护相关队列consumer订阅信息并消费针对步骤一发送的原理可看该文章链接: RocketMQ消息发送针对步骤三接收的原理可看该文章链接: RocketMQ消息接收针对步骤二RocketMQ收到consumer发过来的消息后具体是怎么保存和维护的可观看本文后续正文内容。以下是本篇文章正文内容一. RocketMQ 消息存储架构该图中有三大核心组件相关介绍见下文CommitLog物理文件的“大熔炉”位置图最左侧那个又高又长、包含了多条消息的柱状图。角色它是消息真正的、唯一的物理存储位置。特点不管你是哪个 Topicorder-topic 还是 coupon-topic不管是哪个 Queue生产者Producer发过来的消息全部一股脑、按照时间顺序追加Sequential Write到这里。由于是顺序写 mmap 映射它的写入吞吐量极高。图上的细节消息存储在 CommitLog 里时是整条消息放进去的包含了 Topic 名称、QueueId、消息体Message Body以及各种属性。ConsumeQueue消费逻辑队列 / 小账本位置图中间那个分成多个小格子的横向结构。角色它是专门给消费者Consumer加速读消息的索引小账本。特点每个 Topic 的每个 Queue 都有一个专属的 ConsumeQueue 文件。图上的细节为了省内存它不存真正的消息内容每个格子固定只有 20个字节CommitLog Offset8字节这条消息在物理大文件 CommitLog 里的绝对字节位置比如在第 1024 字节处。Size4字节这条消息的体积有多大比如256字节。Message Tag HashCode8字节消息 Tag 的哈希值用来在过滤消息时提速。IndexFile哈希索引文件 / 业务查日志神器位置图最下方那个带 Slot Table槽位表和 Index Linked List索引链表的结构。角色专门提供给程序员根据消息的 Key如订单号或者 MsgId 去后台查询消息用的。特点底层采用类似 Java HashMap 的 哈希槽 链表 结构。当你想要在控制台查一笔订单的轨迹时不用去翻动整张磁盘直接来查 IndexFile微秒级返还数据位置二. 图中关键细节注意看图中从 CommitLog 指向 ConsumeQueue 和 IndexFile 的那两根标着 dispatch 的箭头。这个动作是 RocketMQ 高性能吞吐的秘密传统 MQ如早期 ActiveMQ的硬伤当生产者发来一条消息时Broker 必须同步地把它写进消息表再同步地去建立一堆索引等全部建完了才返回给生产者成功。这在高并发下会严重拖慢生产者的速度。RocketMQ 的解法高并发“读写分离”写操作主线程生产者把消息发过来RocketMQ 用 mmap 把消息丢进 CommitLog 尾部立刻、马上给生产者返回“发送成功”。整个过程连 1 毫秒都不到建索引后台分发守护线程ReputMessageService 在后台有一个常驻的守护线程就像图里的那个 dispatch箭头一样疯狂、不知疲倦地死循环盯着 CommitLog。①. 一旦发现 CommitLog 里新加入了一条消息它立刻把消息的物理位置、大小、Tag 提取出来异步地、顺时针分发给对应的 ConsumeQueue 和 IndexFile。②. 即使后台建索引的线程因为系统太忙稍微卡了 0.1 秒也完全不会阻碍和拖慢上游生产者的写入速度。小问题commitLog在磁盘吗答是的CommitLog 绝对、百分之百是存放在服务器的磁盘里的 不仅是 CommitLog图上的 ConsumeQueue 和 IndexFile 统统都是持久化在磁盘上的文件。它们在磁盘里的真实路径如果你登录到运行 RocketMQ Broker 的 Linux 服务器上进入默认的存储目录通常是 ~/store/你会发现它们就是硬生生的、看得见摸得着的磁盘文件/home/rocketmq/store/ ├── commitlog │ ├── 00000000000000000000 -- 1G 大小的真实磁盘文件 │ └── 0000000000001073741824 -- 写满了 1G 自动切出的第二个磁盘文件 ├── consumequeue │ └── order-topic │ └── 0 -- 队列0的磁盘索引文件 └── index └── 20260706114254 -- 真实磁盘哈希索引文件既然在磁盘为什么能像写内存一样快重新闭环 mmap答如果每次生产者发消息RocketMQ 都老老实实地去驱动机械硬盘的磁头转一圈、写一下磁盘那它的 QPS每秒吞吐量顶多也就几百系统早就卡死了。它之所以能在磁盘上跑出每秒十几万的并发正是因为 mmap 零拷贝。消息塞进内存后Broker 立刻给上游返回“发送成功”。而真正把数据写进磁盘的脏活累活交给了 Linux 内核的 PageCache 异步刷盘线程或者 RocketMQ 自己的刷盘守护线程。系统会在后台默默地把内存里的数据真正源源不断地同步刷新到磁盘的 CommitLog 物理文件里。三. 从生产者-存储-消费者完整流程进场流程网络→ \rightarrow→磁盘由 mmap 统治第一步Producer 发送消息通过网络网卡→ \rightarrow→驱动内核→ \rightarrow→Socket 缓冲区到达 Broker。第二步Broker 的 Java 进程通过 mmap 技术让内核把 Socket 缓冲区的数据直接映射写入到内核的 PageCache 中。第三步Broker 触发异步通知响应上游 Producer 收到成功。第四步Broker 在后台默默地把 PageCache 里的东西沉淀到磁盘的 CommitLog 中同时 dispatch 到 ConsumeQueue消费队列 和 IndexFile。出场流程磁盘→ \rightarrow→网络由 sendfile 统治第五步Consumer 通过网络向 Broker 请求“我要读 XX Topic 的第几条消息。”第六步Broker 去查对应的 ConsumeQueue 文件在内存里瞬间精准拿到消息在 CommitLog 里的物理偏移量和大小。第七步Broker 在 Java 层调用 transferTo()底层触发 sendfile 零拷贝。第八步Linux 内核直接去磁盘的 CommitLog 捞出这块数据放进 PageCache然后在内核内部直接横转塞给 Socket 缓冲区最后交给网卡通过网络砸给 Consumer补充为什么写用 mmap读用 sendfile答写的时候mmap我们需要让消息从网络缓冲区里出来安全地、持久地落进磁盘。mmap 能够让 Java 进程直接在 PageCache 里追加和组织消息格式比如加上时间戳、MsgId非常灵活。读的时候sendfile消息在磁盘里已经格式化死、定型了不需要做任何修改只需要像流水一样赶紧送给网卡。这时候不需要 Java 介入的 sendfile 就是速度最快的王者。