概述

  • 概述
    • 定位
      • RocketMQ 是一个分布式消息中间件,用于在分布式系统中以异步方式传递时间/数据
    • 典型问题
      • 异步解耦
        • 场景:下单后做扣库存、发优惠券、发短信、写审计日志
        • 没用 MQ:下单接口串行调用 N 个下游 -> 延迟高、耦合重、下游挂了全挂
        • 用 MQ:下单服务只负责写订单 + 发消息,下游异步消费,链路变短、隔离故障、便于扩展
      • 削峰填谷
        • 场景:大促瞬时 QPS 飙升,下游撑不住
        • 用 MQ:把瞬时峰值写入 MQ,消费者按能力稳定拉取 -> 系统不被打爆
      • 广播/集群消费
        • 广播:配置刷新、缓存预热、全量通知
        • 集群:订单履约、发货、对账(同组只要处理一次)
      • 顺序/延迟/事务
        • 顺序:同一订单状态流转必须按照顺序处理
        • 延迟:30 分钟未支付自动关单
        • 事务:下单写库 + 发消息要一致性
    • 领域模型
      • Topic/MessageQueue(并发与顺序的载体)
        • Topic:逻辑分类
        • MessageQueue(队列/分区):Topic 下的物理分片
          • 并发的本质:多个队列=>多个消费者实例并行消费
          • 顺序的边界:只保证同一队列内有序,跨队列无需
          • 原因:MessageQueue 带来了水平扩展吞吐(分片并行)+提供队列内顺序语义
        • Producer 生产者
          • 生产者发送消息时,核心要决定:这条消息进入哪个队列
            • 普通消息:轮询/负载均衡选择队列(提升吞吐)
            • 顺序消息:按业务 key 做一致性路由到同一队列,保证顺序
        • ConsumerGroup 消费组
          • 消费组:一组消费者实例共享同一套消费进度
          • 集群消费:同组内分摊队列
          • 广播消费:每个实例都处理全量消息
          • 同一个 Topic,两个不同的 ConsumerGroup 相当于两套独立的订阅者,各自消费一份完整消息流
    • 卖点
      • 高吞吐
        • 顺序写:消息落到 CommitLog -> 磁盘友好
        • 零拷贝/高效网络传输:减少内核态/用户态拷贝开销
        • 批量与异步:批量写入、批量拉取、异步刷盘/复制
        • 读写分离:写走 CommitLog,读通过索引定位
      • 可扩展
        • Topic 多队列 -> 消费端扩容实例可提升并发
        • Broker 集群扩容 -> 分摊存储与 I/O 压力
        • NameServer 做路由发现 -> Producer/Consumer 动态感知集群变化
        • 靠队列分片 + Broker 横向扩展 + 消费组负载均衡
      • 语义能力
        • 至少一次投递与重复消费
          • RocketMQ 工程实践通常是至少一次:可能重复,但不丢
          • 业务必须幂等
        • 重试与死信
          • 消费失败 -> 重试
          • 达到最大次数仍然失败 -> 进入死信队列
          • 生产实践:DLQ 监控高进 + 人工/自动补偿 + 重新投递
      • 事务消息(最终一致性)
        • 目标:本地事务与消息发送一致
        • 核心机制:半消息(先存 broker 不投递)-> 本地事务 -> commit 才投递;不确定则回查
        • 它解决的是最终一致性,不是强一致 ACID

核心概念

  • 核心概念
    • Topic
      • 定义:消息的逻辑分类,Producer 发送到 Topic,Consumer 订阅 Topic。
      • 关键点:Topic 本身不决定并发,并发来自 Topic 下的 MessageQueue 数量
      • 一个 Topic 通常会在多个 Broker 上分布存储,每个 Broker 上持有一些队列分片
    • MessageQueue
      • 定义:Topic 的物理分片,是投递与顺序的基本单元。
      • 并发能力=队列数上限:同哦个 ConsumerGroup 里,一个队列同一个时刻只会被一个消费者实例消费
      • 顺序边界:RocketMQ 的顺序语义通常是队列内有序,跨队列不保证
      • 路由载体:Producer 发送时必须选择一个队列(轮询/哈希/自定义)
    • Producer 生产者
      • 职责:构造消息、选择 Topic 队列、发送、处理失败重试
      • Producer 会先拿到 Topic 的路由信息(队列分布在哪些 Broker)
      • 发送失败通常会重试/换 Broker(取决于客户端策略与路由状态)
      • 发送模式
        • 同步:拿到 sendResult 才返回(最常用)
        • 异步:回调(高吞吐/低延迟)
        • 单向:不关心结果(日志/打点)
    • Consumer(消费者)
      • 职责:拉取消息(Pull 体系,Push 是封装)、本地处理、成功后提交 offset,失败触发重试
      • RocketMQ PushConsumer 本质是客户端内部循环 Pull + 回调你业务代码
      • 消费并发:由消费线程数 + 队列分配决定
    • ConsumerGroup(消费组)
      • 定义:一组消费者实例,共享同一套消费进度(offset),一起消费 Topic。
      • CLUSTERING(集群消费):同组分摊消费(每条只处理一次)
      • BROADCASTING(广播消费):每个实例都处理全量(各自进度)
    • NameServer
      • 定位:路由注册与发现(轻量注册中心,不像 Kafka 把协调状态放在上面)
      • Broker 启动后像 NameServer 注册:Topic -> Broker -> 队列信息
      • Producer/Consumer 定期从 NameServer 拉取最新路由
      • NameServer 挂了是否可以发消息
        • 短期通常能发(客户端还有路由缓存),但路由无法更新;长期不行,且新 Topic/新 Broker 不可见。
    • Broker
      • 职责:接收消息,持久化存储,为消费者提供拉取、维护消费进度相关能力、做复制。
      • 消息一般先写 CommitLog (顺序写),再构建队列索引。
      • 吞吐高的原因:顺序写+批量+高效 IO 路径
    • Offset (消费进度)
      • 定义:ConsumerGroup 在每个 MessageQueue 上的消费位置
      • 成功消费 -> 提交 offset
      • 失败 -> 不提交 -> 后续重试/再次提交
    • 数据流
      • 生产链路
        • 拿路由
          • Producer 启动后从 NameServer 拉取 Topic 路由:有哪些 Broker、每个 Broker 上有哪些队列
        • 选队列
          • Produer 根据策略选择一个 MessageQueue
          • 普通消息:轮询/负载均衡
          • 顺序消息:按业务 key 哈希到固定队列
        • 发送到 Broker
          • Producer 把消息发送到队列所在 Broker
        • 持久化与应答
          • Broker 持久化成功后返回 sendResult,Producer 认为发送成功
      • 消费链路
        • 订阅与分配
          • Consumer 加入 ConumserGroup,订阅 Topic
          • 在集群模式下,同组实例会进行队列分配:每个 MessageQueue 分配给某个实例
        • 拉取
          • 被分配到队列的 Consumer 维护该队列的 offset,向 Broker 发起拉取请求
          • 拉取这个队列从 offset=N 开始的下一批消息
        • 本地处理
          • Consumer 收到消息后调用你的业务处理逻辑(线程池并发执行)
        • 提交 Offset
          • 业务成功 -> 提交 offset(推进进度)

可靠性、语义、幂等、重试、死信

  • 可靠性/语义/幂等/重试/死信
    • 消息语义
      • 三种语义
        • At-most-once(至多一次):不重复,但可能丢
        • At-least-once(至少一次):尽量不丢,但可能重复(RocketMQ 采用)
        • Exactly-once(恰好一次):不丢不重
    • 为什么会重复
      • 本质:分布式不确定性,无法在网络超时常见里判断对方是否已经成功
      • 生产端导致重复:超时不等于失败
        • Producer 发出了 send 请求
        • Broker 实际写成功了,但 ack 在路上丢了/Producer 超时
        • Producer 认为失败 -> 重试发送
        • Broker 上存在两条业务等价消息
      • 消费端导致重复:处理成功但提交 offset 失败/延迟
        • Consumer 拉到消息并执行成功
        • 在提交 offset 前发生进程重启/网络抖动/提交失败
        • 下次启动从旧 offset 拉 -> 同一消息再次被处理
    • 重试与死信:失败后到底怎么流转
      • 消费失败 -> 重试:两类消费方式的区别
        • 并发消费:消息可并行处理;失败后会触发稍后重投
        • 顺序消费:同队列内严格顺序;失败会卡住队列,知道成功或打到策略
      • 重试次数与延迟
        • 消费失败后,消息会进入“重试主题/重试队列“
        • 每次重试都会有一定的延迟
        • 达到最大重试次数后仍然失败 -> 进入死信队列
        • DLQ 常见命名 %DLQ%
      • DLQ 工程治理
        • 监控报警
          • DLQ 堆积量、进入 DLQ 的速率、重试次数分布、消费失败率
        • 分类原因
          • 可重试:下游超时、偶发网络、锁冲突
          • 不可重试:参数非法、业务校验失败、数据缺失
    • Offset 管理
      • Offset 是什么
        • Offset 是 ConsumerGroup 在每个 MessageQueue 上的消费位置
        • 不是全局一个值,而是 (group,topic,queueId) -> offset
      • 正确提交时机
        • 处理成功后再提交

性能与扩展、顺序与分区、堆积治理

  • 性能与扩展、顺序与分区、堆积治理
    • 吞吐与并发怎么调
      • 吞吐链路拆解,多个环节的 min
        • Producer 发送能力
        • Broker 写入能力
        • Consumer 拉取与处理能力
        • 队列分片与分配:队列数决定
      • 并发的硬上限:Queue 数决定可扩展性
        • 集群消费模式下,同一个 ConsumerGroup 内,一个 MessageQueue 同一时刻只会分配给一个消费者实例
        • 有效消费实例并行度 <= 队列数 Q
        • 有 C 个实例:有效利用 = min(Q,C)
        • 线程数只能提高单实例处理并行,队列数太少时,扩容实例不涨吞吐
      • 调整的三类杠杆
        • 队列数
          • 现象:加消费者实例吞吐不涨/涨很少
          • 原因:Q 太小,很多实例分不到队列
          • 操作:提高 Topic 队列数
        • 消费者实例数
          • 前提:Q 足够
          • 收益:线性提升
        • 消费者线程数/批量参数
          • 适用 Q 足够但实例受限或业务处理轻的场景
    • 堆积定位
      • 堆积的数学本质
        • 堆积增长 = 生产速率 > 消费速率,差值积累在 broker
      • 堆积原因分类
        • 消费变慢
        • 失败重试太多
        • 生产突增
        • Broker 压力抖动
      • 堆积排查步骤
        • 确定堆积是全局还是局部
          • 如果是局部队列对接,大概率是热点 key/顺序路由/某实例异常
        • 消费失败率与重试量
          • 失败率高 -> 先止血,否则越修越崩
          • 识别同一类异常栈是否集中
        • 看单消息处理耗时分布
          • 平均不重要,重点看 p95/p99
          • p99 高往往来自:慢 SQL、远程调用尾延迟、锁竞争
        • 看下游容量
        • 看队列数与分配
        • 看 Broker 层资源
      • 堆积治理手段
        • 止血
          • 限流生产端:控制入口
          • 失败隔离:把异常消息旁路到补偿 Topic/DLQ,避免拖垮主消费
          • 降级业务逻辑:先做核心动作,其他异步/延后
        • 恢复吞吐
          • 扩容消费者实例
          • 提高批量拉取/批量处理
          • 优化热点:把热点 key 拆散
          • 若某些队列极端堆积:考虑迁移/重新分配队列所在 broker
        • 根治
          • 优化热点:慢 SQL、缓存、异步化、批处理
          • 调整队列数与 key 策略
          • 建立 DLQ/补偿闭环与告警
          • 压测与容量规划:明确峰值可承受的 TPS 与延迟

存储与高可用

  • 存储与高可用
    • Broker 存储核心
      • 三类文件
        • CommitLog:消息本体的唯一真相源
          • 所有 Topic 的消息都会追加吸入 CommitLog(不是每个队列一个日志,而是统一日志)
          • 写入模型是 append-only(追加写),是 RocketMQ 的高吞吐基础之一
        • ConsumeQueue:面向消费的队列索引
          • 消费者按照 MessageQueue 语义来消费,CommitLog 是全局混合写入的,需要一个队列视角的索引结构把队列映射回 CommitLog
          • ConsumeQueue 的本质是:每个 (topic, queueId) 一套逻辑队列索引,记录这条队列消息在 CommitLog 的物理位置等信息
          • ConsumeQueue 是可重建的-Broker 异常后基于 CommitLog 恢复并重建缺失的 ConsumeQueue 索引,因此 ConsumeQueue 对绝对不丢要求没那么高
        • IndexFile:按 key 查询的哈希索引
          • 用于按 message key 做快速查询
      • 写入流程
        • 流程
          • Producer 把消息发送到 Broker
          • Broker 顺序追加写 CommitLog
          • 再异步/批量地更新 ConsumeQueue/IndexFile
        • 原因
          • 顺序写 CommitLog 最快
          • 索引可以延迟/批量构建,并且 ConsumeQueue 丢了也能重建
      • 读取链路
        • 消费者按队列 (topic, queueId) 消费
        • Consumer 拉取时携带 offset
        • Broker 通过 ConsumeQueue 把队列 offset 映射到 CommitLog 的物理位置
        • 再从 CommitLog 把消息读出来返回给 Consumer
      • 吞吐高:顺序写+mmap+零拷贝+批量
        • 顺序写:CommitLog append-only 模式写磁盘
        • mmap 内存映射与 MappedFile 抽象
          • Broker 用 mmap 把文件映射为虚拟内存,写入像写内存一样,减少传统 read/write 的系统调用与拷贝成本
        • 零拷贝方向
        • 批量
          • 批量写入、批量拉取能显著摊薄网络往返与系统调用开销
    • HA:复制、切换、延迟与一致性取舍
      • 传统 Master/Slave:复制延迟与一致性
        • Master 负责写入,Slave 复制数据做冗余
        • 关键变量:复制是同步还是异步,直接决定
          • RPO(数据丢失窗口):异步复制可能丢失最近一段
          • RTO(恢复时间):切换/重连需要时间
      • 自动主从切换:Controller
        • Controller:把谁是主、怎么选主、怎么切这个协调逻辑集中管理,从而支持自动故障转移
        • 切换时关注
          • 选主/仲裁机制
          • 路由更新
          • 复制进度
          • 业务容忍度
      • RocketMQ-on-DLedger(Raft):更强一致的 HA 思路
        • DLedge 把 CommitLog 的复制变成 Raft 日志复制,从而提高一致性并提供自动选主
        • 代价:写路径更重,延迟更高,但一致性更强、切换更自动化