上回说到了commitlog与consumequeue,那么他们之前是怎么联系的呢,这就是咱们这篇的主题了,当然之前写的文章如果有任何问题,欢迎大家批评指教;写完这个之后还是的先写索引,不过已经这么写了就这样吧,后面补上,大手空中一挥:欠一篇
┌───────────────────────────────────────── 写流程 ─────────────────────────────────────────┐
│ │
│ Producer → 发送消息 → Broker → 写入 CommitLog → 异步线程 ReputMessageService → 构建 ConsumeQueue │
│ │
└────────────────────────────────────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────── 读流程 ─────────────────────────────────────────┐
│ │
│ Consumer → 请求 Topic-QueueId 消息 → Broker → 读取 ConsumeQueue 索引 → 定位 CommitLog → 返回消息 │
│ │
└────────────────────────────────────────────────────────────────────────────────────────────┘
写流程
┌───────────────────────────────────────────────────────────────────┐
│ Step 1:消息写入 CommitLog 成功 │
│ (生成全局唯一物理偏移量、消息长度、Tag哈希值) │
│ │
│ Step 2:ReputMessageService 监听 CommitLog 新消息 │
│ (从 CommitLog 中解析出 Topic、QueueId、Tag 等信息) │
│ │
│ Step 3:构建 20 字节 ConsumeQueue 索引项 │
│ ┌──────────┬──────────┬──────────┐ │
│ │物理偏移量│ 消息长度 │ 标签哈希 │ ← 写入 ConsumeQueue │
│ │ 8字节 │ 4字节 │ 8字节 │ │
│ └──────────┴──────────┴──────────┘ │
│ │
│ Step 4:索引项追加写入对应 Topic-QueueId 的 ConsumeQueue 文件 │
│ → 文件未满:直接追加到末尾 │
│ → 文件已满:自动新建文件(文件名=当前逻辑偏移量) │
│ │
│ Step 5:更新 ConsumeQueue 逻辑偏移量(记录下一个索引项位置) │
└───────────────────────────────────────────────────────────────────┘
咱们看到了ReputMessageService监听commitlog,说到监听,这个词本身就有点异步的味道,像警察叔叔蹲守XX,那不可能一天24小时面对面一直盯着跟着,那能抓到吗?是不是,咱们ReputMessageService异步构建索引,然后后面的工作人员再悄悄地写入,而消息你到达commitlog了之后你就可以转脸向producer邀功,让她看看你有多快!我ReputMessageService来将消息真正的落索引生根,否则同步到磁盘这io、重操作大家都干等着太熬人了 是吧,而且这样一来异步也可以等攒一波了再刷盘,这样效率是不是更高了,处处都是智慧,真厉害。
这个地方有点重要,咱们再细说一下:
┌───────────────────────────────────────────────────────────────────┐
│ 启动阶段 │
│ ┌─────────────┐ ┌─────────────┐ ┌────────────────────────┐ │
│ │ Broker 启动 │ → │ 初始化线程 │ → │ 设置 reputFromOffset │ │
│ │ │ │ 并启动 │ │ 为 CommitLog 最大偏移 │ │
│ └─────────────┘ └─────────────┘ └────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────────┐
│ 循环工作阶段(核心) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 1:检查线程运行状态(isRunning 是否为 true) │ │
│ │ ├─ 否 → 退出循环,线程终止 │ │
│ │ └─ 是 → 进入下一步 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 2:获取 CommitLog 最新写入偏移量(maxPhyOffset) │ │
│ │ ├─ reputFromOffset ≥ maxPhyOffset → 无新消息 │ │
│ │ │ └─ 休眠 sleepTimeInMillis,跳回 Step 1 │ │
│ │ └─ reputFromOffset < maxPhyOffset → 有新消息 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 3:从 reputFromOffset 读取 CommitLog 消息 │ │
│ │ ├─ 解析消息:Topic、QueueId、Tag、消息长度、物理偏移 │ │
│ │ ├─ 校验消息合法性(Magic Code + CRC32) │ │
│ │ └─ 校验失败 → 跳过此消息,reputFromOffset 后移 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 4:构建 ConsumeQueue 索引项(20字节) │ │
│ │ ┌──────────┬──────────┬──────────┐ │ │
│ │ │物理偏移量│ 消息长度 │ 标签哈希 │ │ │
│ │ └──────────┴──────────┴──────────┘ │ │
│ │ ├─ 写入对应 Topic-QueueId 的 ConsumeQueue 文件 │ │
│ │ └─ 更新 ConsumeQueue 逻辑偏移量 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 5:构建 IndexFile 索引(可选,按 Key 索引) │ │
│ │ ├─ 判断消息是否带 Key(如 messageKey) │ │
│ │ ├─ 是 → 计算 Key 哈希,写入 IndexFile 索引 │ │
│ │ └─ 否 → 跳过此步骤 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 6:更新 reputFromOffset 到下一条消息的物理偏移 │ │
│ │ └─ 跳回 Step 1,继续循环处理 │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────────┐
│ 终止阶段 │
│ ┌─────────────┐ ┌─────────────┐ ┌────────────────────────┐ │
│ │ Broker 关闭 │ → │ 设置 isRunning=false │ → 线程退出循环 │ │
│ │ │ │ │ │ 资源释放(内存映射) │ │
│ └─────────────┘ └─────────────┘ └────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
诶,咱们这是不是多看到了reputFromOffset,这很重要:ReputMessageService 线程通过 reputFromOffset 检测到 CommitLog 有新消息,读取并解析消息的 Topic、QueueId、Tag 等信息,异步构建 ConsumeQueue 索引项!
当然这个过程还有很多细节要说
不过呢先到这里吧,太多了也不容易理解;
读流程
相对来说,读就简单了,人家要消费那就是上帝,怎么能让上帝感觉麻烦呢是不是,那有点不懂事了:
┌───────────────────────────────────────────────────────────────────┐
│ Step 1:Consumer 向 Broker 发送拉取请求 │
│ (携带参数:Topic、QueueId、消费偏移量 offset) │
│ │
│ Step 2:Broker 根据 offset 定位 ConsumeQueue 文件 │
│ → 计算文件位置:offset ÷ 单文件最大索引项数 │
│ → 计算文件内位置:offset % 单文件最大索引项数 │
│ │
│ Step 3:读取 20 字节索引项,解析关键信息 │
│ ┌──────────┬──────────┬──────────┐ │
│ │物理偏移量│ 消息长度 │ 标签哈希 │ ← 读取并解析 │
│ │ 8字节 │ 4字节 │ 8字节 │ │
│ └──────────┴──────────┴──────────┘ │
│ │
│ Step 4:Tag 过滤(可选) │
│ → Consumer 指定 Tag:对比索引项 Tag 哈希值,不匹配则跳过 │
│ → 未指定 Tag:直接进入下一步 │
│ │
│ Step 5:根据物理偏移量+消息长度,从 CommitLog 读取完整消息 │
│ │
│ Step 6:Broker 将消息返回给 Consumer,更新消费进度 │
└───────────────────────────────────────────────────────────────────┘
我们看到consumer同步拉取咱们的索引,定位到commitlog,借助索引的力量避免全量扫描;
全流程
下面很详细,我就不说了,适当的退场是对思考的无声的尊重。
┌─────────────────┐ ┌─────────────────────────┐ ┌─────────────────┐
│ Producer 线程 │ │ Broker 进程 │ │ Consumer 线程 │
└────────┬────────┘ └───────────┬─────────────┘ └────────┬────────┘
│ │ │
│ 1. 发送消息到 Broker │ │
│─────────────────────────────> │
│ │ │
│ │ ┌─────────────────────────┐ │
│ │ │ CommitLog 写入线程 合法性校验 │ │
│ │ │ - 顺序写入 CommitLog 内存 生成偏移量长度等元数据 │ │
│ │ └───────────┬─────────────┘ │
│ │ │ │
│ │ ┌───────────▼─────────────┐ │
│ │ │ FlushCommitLogService │ │
│ │ │ - 异步刷盘 CommitLog 批量 │ │
│ │ └───────────┬─────────────┘ │
│ │ │ │
│ │ ┌───────────▼─────────────┐ │
│ │ │ ReputMessageService │ │
│ │ │ -拿着reputfromoffset 监听 CommitLog 新消息 │ │
│ │ │ - 解析消息topic\queueid\tag异步构建 ConsumeQueue 索引│ │
│ │ └───────────┬─────────────┘ │
│ │ │ │
│ │ ┌───────────▼─────────────┐ │
│ │ │FlushConsumeQueueService │ │
│ │ │ - 异步刷盘 ConsumeQueue │ │
│ │ └───────────┬─────────────┘ │
│ │ │ │
│ │ ┌───────────▼─────────────┐ │
│ │ │ PullMessageProcessor │ │
│ 3. 返回消息给 Consumer │ │ - 接收 Consumer 拉取请求│ │ 2. 拉取消息
│<────────────────────────────┼──│ - 读 ConsumeQueue 索引 │◄───────────
│ │ │ - 定位 CommitLog 消息 │ │
│ │ └─────────────────────────┘ │
│ │ │
│ │ │
赠送:
因为咱们rocketmq的commitlog与consumequeue的精心设计,他们之间若隐若离,看似没有关系很解耦合,但是又剪不断千丝万缕的情丝,甚至当ConsumeQueue 损坏 ,ReputMessageService 可以从头遍历 CommitLog,重构 ConsumeQueue 索引,因为consumequeue是不是20个字节,就那三个东西,你说哪一个咱们commitlog没有!都有所以你坏了,读不了了,咱也不需要依赖原有索引文件,是不是很坚强!
咱们的ReputMessageService本身也很贴心,不仅是对别人更是对自己:比如当commitlog写入过快,ReputMessageService焦头烂额的时候,他就给自己放假:自动调整休眠时间,忙不过来明显是睡得不够,需要多休息,这样也释放了cpu,把资源留给更需要的commitlog,这样二全齐美、一箭双雕,不得不为咱们ReputMessageService点赞,就是不知道他这种思维和工作方式怎么让我的老板知道「🤔️」
不过多休息也不行了,当ReputMessageService不堪重负直接瓜了,怎么办?没关系、咱们rocketmq能想不到吗,还有broker这个总指挥大领导,当 ReputMessageService 线程异常挂掉 ,Broker 会自动重启该线程,并从 reputFromOffset 断点继续处理,所以就不会重复或遗漏索引构建。