3. RocketMQ 索引架构:CommitLog 与 ConsumeQueue 的关系、索引文件结构(IndexFile)、索引构建流程

RocketMQ 的索引体系是性能调优的核心。理解 CommitLog、ConsumeQueue 与 IndexFile 三者的协作关系,是诊断消息读写慢、消费堆积、查询延迟等问题的前提。本节从文件结构、关联机制、构建流程三个维度展开。

3.1 CommitLog:消息的物理存储主体

CommitLog 是 RocketMQ 中所有消息的顺序写入文件,每个 Broker 只有一个 CommitLog 目录(默认 $HOME/store/commitlog),所有 Topic 的消息按到达顺序追加写入。

属性 说明
文件命名 文件名 = 起始偏移量,固定 1GB 大小,如 00000000001073741824
写入方式 顺序追加写,无锁或轻量锁,磁盘 IO 友好
消息体 包含消息长度、物理偏移量、QueueId、QueueOffset、消息属性、消息体等
刷盘策略 同步刷盘 / 异步刷盘,直接影响写入延迟
调优要点:CommitLog 的写入性能决定了整个 Broker 的吞吐上限。异步刷盘 + 页缓存(PageCache)写是高性能场景的标配;同步刷盘适用于金融级强一致性场景,但 TPS 会下降 40%~60%。

3.2 ConsumeQueue:逻辑消费队列

ConsumeQueue 是基于 Topic 和 QueueId 组织的逻辑索引文件,每个 ConsumeQueue 文件固定存储 30 万条索引条目(约 5.72MB)。它不存储消息体,只存储三条关键信息:CommitLog 物理偏移量、消息长度、消息 Tag 的哈希值。

字段 字节数 说明
CommitLog Offset 8 消息在 CommitLog 中的起始物理偏移量
Message Size 4 消息体长度(字节)
Tag HashCode 8 消息 Tag 的 32 位哈希值,用于消息过滤

消费者拉取消息时,先读 ConsumeQueue 获取偏移量,再根据偏移量到 CommitLog 中读取完整消息体。这种设计将随机读转换为顺序读 + 一次随机读,大幅降低 IO 开销。

性能诊断提示:如果 ConsumeQueue 文件出现大量空洞(即索引指向的 CommitLog 偏移量无效),会导致消费者频繁空拉,增加网络开销和 CPU 空转。检查 broker.log 中是否有 "offset not matched" 类日志。

3.3 IndexFile:消息的二级索引

IndexFile 用于按消息 Key 或时间范围快速查询,是 RocketMQ 实现“精确查询”功能的基础。每个 IndexFile 固定 400MB,包含 500 万条索引槽位。

区域 大小 内容
IndexHeader 40 字节 beginTimestamp、endTimestamp、beginPhyOffset、endPhyOffset、hashSlotCount、indexCount
Hash Slots 500 万 × 4 字节 哈希槽,存储指向 IndexItem 链表的头指针
Index Items 500 万 × 20 字节 每个条目包含:keyHash、phyOffset、timestampDiff、preIndexNo

IndexFile 采用哈希 + 链表结构解决哈希冲突。查询时先计算 Key 的哈希值定位 Hash Slot,再遍历链表找到匹配的物理偏移量。

慢查询风险:当 IndexFile 中某个 Hash Slot 下的链表过长(例如大量消息使用相同 Key),查询会退化为线性扫描,单次查询可能超过 100ms。建议对高频 Key 做业务层分桶,或控制单 Key 消息量不超过 10 万条。

3.4 索引构建流程

消息从生产者发送到消费者消费,索引的构建分为实时写入和异步构建两个阶段。下图展示了完整的索引构建链路:

RocketMQ 索引构建流程 生产者发送消息 ① 顺序写入 CommitLog 物理存储,所有 Topic 共享 ② 异步构建 ConsumeQueue 按 Topic + QueueId 组织索引 ③ 异步构建 IndexFile 按消息 Key 哈希索引 消费者拉取消息 说明:ConsumeQueue 和 IndexFile 的构建由 ReputMessageService 异步线程完成,不阻塞 CommitLog 写入

3.5 索引构建的异步机制

CommitLog 写入完成后,ReputMessageService 线程会持续扫描 CommitLog 中新增的数据,并执行以下操作:

  1. 解析消息:从 CommitLog 中读取消息的 Topic、QueueId、QueueOffset、Tag、Key 等属性。
  2. 分发到 ConsumeQueue:根据 Topic 和 QueueId 找到对应的 ConsumeQueue 文件,追加写入索引条目(偏移量、长度、Tag 哈希)。
  3. 分发到 IndexFile:如果消息包含 Key,则计算 Key 的哈希值,写入 IndexFile 的 Hash Slot 和 Index Item 链表。
  4. 更新消费进度:更新 Broker 内部的消费队列最大偏移量(MaxOffset),供消费者拉取时参考。
调优参数:reputMessageServiceThreadNum(默认 1)控制 ReputMessageService 的线程数。在超高吞吐场景(单 Broker 写入 > 5 万 TPS),建议调整为 2~3,避免索引构建成为瓶颈。同时监控 reputBehind 指标,若持续增长说明索引构建跟不上写入速度。

3.6 三种索引的对比总结

维度 CommitLog ConsumeQueue IndexFile
存储内容 完整消息体 + 元数据 偏移量 + 长度 + Tag 哈希 Key 哈希 + 偏移量 + 时间戳
文件数量 1 个目录,按偏移量分文件 Topic × Queue 数量 固定 1 个文件(滚动)
写入方式 顺序追加 顺序追加 哈希 + 链表追加
读取方式 随机读(通过偏移量定位) 顺序读 + 一次随机读 哈希查找 + 链表遍历
主要用途 消息持久化、数据恢复 消费者拉取消息 按 Key / 时间范围查询
性能敏感点 刷盘策略、PageCache 命中率 空洞率、文件数过多 哈希冲突、链表长度

3.7 慢查询诊断:索引层面的常见问题

  • ConsumeQueue 文件过多:每个 Topic 的每个 Queue 都会生成 ConsumeQueue 文件。当 Topic 数量 > 100 且 Queue 数量 > 8 时,文件数可能超过 10 万,导致文件句柄耗尽和 IO 调度延迟。建议控制 Topic 总数在 50 以内。
  • IndexFile 哈希冲突严重:使用 mqadmin queryMsgByKey 命令时,如果返回时间 > 50ms,说明 Hash Slot 链表过长。可通过 indexFile.forceFlushInterval 参数调整刷盘频率,或减少单 Key 消息量。
  • ReputMessageService 积压:监控 reputBehind 指标,如果持续大于 10 万,说明索引构建线程处理不过来。优先检查 CPU 是否打满,再考虑增加线程数。
  • CommitLog 与 ConsumeQueue 不一致:异常断电可能导致 ConsumeQueue 指向已损坏的 CommitLog 偏移量。此时消费者会报 Message not found 错误,需要执行 mqadmin repairConsumeQueue 修复。
最佳实践:在压测阶段,建议开启 transientStorePoolEnable(堆外内存缓冲池)和 enableConsumeQueueExt(扩展 ConsumeQueue 存储 Tag 全量信息),可减少索引构建的 GC 压力,提升整体吞吐 15%~20%。