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 索引构建流程
消息从生产者发送到消费者消费,索引的构建分为实时写入和异步构建两个阶段。下图展示了完整的索引构建链路:
3.5 索引构建的异步机制
CommitLog 写入完成后,ReputMessageService 线程会持续扫描 CommitLog 中新增的数据,并执行以下操作:
- 解析消息:从 CommitLog 中读取消息的 Topic、QueueId、QueueOffset、Tag、Key 等属性。
- 分发到 ConsumeQueue:根据 Topic 和 QueueId 找到对应的 ConsumeQueue 文件,追加写入索引条目(偏移量、长度、Tag 哈希)。
- 分发到 IndexFile:如果消息包含 Key,则计算 Key 的哈希值,写入 IndexFile 的 Hash Slot 和 Index Item 链表。
- 更新消费进度:更新 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%。