消息队列架构:Kafka / RabbitMQ / RocketMQ 拓扑与顺序保证
消息队列是分布式系统的粘合剂——Kafka 走吞吐、RabbitMQ 走灵活、RocketMQ 走事务。这篇是三种主流 MQ 的架构拓扑、consumer group、partition 顺序保证、exactly-once 语义、消息积压排查、dead-letter 实战。
三种 MQ 对比
维度
Kafka
RabbitMQ
RocketMQ
起源
LinkedIn
Erlang/金融
阿里
模型
分布式 log
队列 + exchange
队列 + topic
吞吐
百万级/秒
万级/秒
十万级/秒
延迟
10-100ms
1-10ms
5-50ms
顺序
partition 内
queue 内(单 consumer)
queue 内
事务
弱(0.11+ 支持)
不支持
支持
适用
日志、流计算
企业消息、灵活路由
电商交易
Kafka 集群架构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @startuml skinparam componentStyle rectangle skinparam defaultTextAlignment center title "Kafka Cluster Architecture" node "Kafka Broker 1" { queue "Topic: orders (Partition 0)" as p1_0 queue "Topic: orders (Partition 1)" as p1_1 queue "Topic: orders (Partition 2)" as p1_2 } node "Kafka Broker 2" { queue "Topic: orders (Partition 0 replica)" as p2_0 queue "Topic: orders (Partition 1 replica)" as p2_1 queue "Topic: orders (Partition 2 replica)" as p2_2 } node "Kafka Broker 3" { queue "Topic: payments (Partition 0)" as p3_0 queue "Topic: payments (Partition 1)" as p3_1 } node "ZooKeeper / KRaft" as ZK { component "Cluster Metadata" as cm } participant "Producer\n(Order Service)" as prod participant "Consumer Group A\n(Inventory Service)" as cg_a participant "Consumer Group B\n(Analytics Service)" as cg_b prod --> p1_0 : "key=user_id" prod --> p1_1 : "key=user_id" prod --> p1_2 : "key=user_id" cg_a --> p1_0 : "consume" cg_a --> p1_1 : "consume" cg_a --> p1_2 : "consume" cg_b --> p1_0 : "consume (独立 offset)" cg_b --> p1_1 : "consume" cg_b --> p1_2 : "consume" prod --> p3_0 cg_a --> p3_0 cg_b --> p3_0 cg_b --> p3_1 ZK <-- p1_0 : "metadata" ZK <-- p1_1 : "metadata" ZK <-- p3_0 : "metadata" @enduml
关键概念 :
Topic — 消息分类(orders payments)
Partition — topic 物理分片,每个 partition 内顺序
Replica — partition 多副本,leader + followers
Consumer Group — 一组消费者,每个 partition 只被 group 内一个消费者消费
Offset — 消费位置,group 独立维护
Kafka 顺序保证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @startuml title "Kafka Ordering Guarantee" skinparam componentStyle rectangle partition "Topic: orders" { queue "Partition 0" as p0 queue "Partition 1" as p1 queue "Partition 2" as p2 } participant "Producer" as Prod participant "Consumer A" as A participant "Consumer B" as B note over Prod key = user_id 同一 user 路由到同一 partition end note Prod -> p0 : order_1 (user=alice) Prod -> p1 : order_1 (user=bob) Prod -> p0 : order_2 (user=alice) Prod -> p2 : order_1 (user=charlie) Prod -> p0 : order_3 (user=alice) note right of p0 p0 内顺序: ① order_1 (alice) ② order_2 (alice) ③ order_3 (alice) ✅ 同一 user 顺序保证 end note p0 --> A : consumer A 消费 p1 --> B : consumer B 消费 p2 --> A note over A, B 不同 partition 之间无序 同一 partition 内有序 end note @enduml
关键 :
同一 key → 同一 partition → 同一 consumer (group 内)
不同 key → 不同 partition → 可能并行
全局顺序无法保证 (除非单 partition + 单 consumer)
Kafka Exactly-Once Semantics (EOS) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @startuml title "Kafka Exactly-Once with Transactional API" participant "Producer" as P participant "Kafka Broker" as K participant "Consumer" as C database "DB" as DB P -> P : ① initTransactions() P -> P : ② beginTransaction() loop 处理 N 条消息 P -> DB : ③ 业务写入(本地事务) P -> K : ④ send offset + record (同一事务) end P -> P : ⑤ commitTransaction() note right of P Kafka 事务保证: - 原子:消费 offset + 业务写要么都成功要么都失败 - 幂等:ProducerId + SequenceNumber 去重 - 不重复:Consumer 读到的是已提交的 end note C -> K : ⑥ read_committed (只读已提交) C -> C : ⑦ process message C -> DB : ⑧ 写结果 C -> K : ⑨ sendToNext (forward) @enduml
EOS 三种实现 :
幂等 Producer — ProducerId 去重,不能跨 producer
事务 API — 跨 partition + offset 原子写
读-处理-写 — Consumer 写入新 topic,下游幂等
RabbitMQ Exchange 拓扑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @startuml title "RabbitMQ Exchange Types" skinparam componentStyle rectangle participant "Producer" as P rectangle "Exchange Types" { component "Direct Exchange\n(routing_key 完全匹配)" as direct component "Topic Exchange\n(routing_key 模式匹配 *.order.*)" as topic component "Fanout Exchange\n(广播所有 queue)" as fanout component "Headers Exchange\n(基于 header 路由)" as headers } queue "Queue: order.new" as q1 queue "Queue: order.paid" as q2 queue "Queue: order.cancelled" as q3 queue "Queue: analytics" as qa queue "Queue: audit" as qau P -> direct : "routing_key='order.new'" direct --> q1 : "routing_key='order.new'" direct --> q2 : "routing_key='order.paid'" direct --> q3 : "routing_key='order.cancelled'" P -> topic : "routing_key='cn.order.new'" topic --> q1 : "匹配 *.order.new" topic --> q2 : "匹配 *.order.paid" topic --> qa : "匹配 *.order.*" P -> fanout : "广播" fanout --> q1 fanout --> q2 fanout --> q3 fanout --> qau @enduml
Exchange 类型选择 :
Direct — 一对一精确路由(订单状态分发)
Topic — 模式路由(多业务域)
Fanout — 广播(配置变更通知)
Headers — 元数据路由(复杂场景)
RabbitMQ 消息确认 + 死信队列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @startuml title "RabbitMQ Dead Letter Queue (DLQ) Flow" participant "Producer" as P queue "Main Queue" as MQ queue "Retry Queue (TTL=30s)" as RQ queue "Dead Letter Queue" as DLQ participant "Consumer" as C P -> MQ : ① publish MQ -> C : ② deliver alt Consumer ACK C -> MQ : ③ ack (success) else Consumer NACK C -> MQ : ④ nack (requeue=false) MQ -> DLQ : ⑤ dead-letter (失败超过 max retries) end note right of MQ x-dead-letter-exchange 配置: 失败消息自动转发到 DLX end note note right of RQ 延迟重试队列: - TTL 到期 - 自动回主队列 end note DLQ -> C : ⑥ 人工 / 监控消费 @enduml
DLQ 适用 :
业务异常重试无效 — 订单重复创建超过 3 次
消息格式错误 — JSON 解析失败
下游服务故障 — 持续 1 小时
RocketMQ 事务消息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @startuml title "RocketMQ Transactional Message" participant "Producer" as P participant "Broker" as B participant "Local DB" as DB participant "Consumer" as C P -> DB : ① 本地事务(创建订单,status=待确认) DB --> P : ② OK P -> B : ③ send half message (半消息,消费者不可见) B --> P : ④ OK P -> DB : ⑤ 执行本地事务 commit DB --> P : ⑥ OK P -> B : ⑦ commit (发送 commit 消息) B --> P : ⑧ OK B -> C : ⑨ 投递消息(消费者可见) C -> C : ⑩ 业务处理(扣库存等) C -> B : ⑪ ACK note right of B Half message 机制: - 消息已落地 - 但对消费者不可见 - 等 producer confirm 后才投递 end note note over P, B : 回查机制 B -> P : ⑫ 反向查询(producer 挂了) P -> B : ⑬ commit / rollback @enduml
RocketMQ 事务 保证本地事务 + 消息发送 的最终一致性——下单成功 → 消息一定会投递 。
消息积压排查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @startuml title "Message Backlog Investigation" start :Kafka consumer lag 突然升高; :看 consumer group lag; note right kafka-consumer-groups.sh --describe --group <name> end note if (lag 单 partition 高) :查该 partition 的 consumer 实例; :看 consumer GC / CPU; :看下游 DB 慢查询; elseif (lag 全 partition 高) :consumer 整体慢; :看 broker 磁盘 IO; :看 network bandwidth; else :查 producer 速率; :突发流量还是真的; end if (consumer 实例不够) :加 consumer 实例 (≤ partition 数); elseif (consumer 处理慢) :优化代码 / 加 batch; :异步写 DB; :并行处理; elseif (消息格式变化) :consumer 解析失败 retry 死循环; :加 DLQ; end :验证 lag 下降; stop @enduml
Lag 排查命令 (Kafka):
1 2 3 4 5 6 7 kafka-consumer-groups.sh --bootstrap-server localhost:9092 \ --describe --group inventory-group
消息顺序实战 场景 :同一订单的「创建 → 支付 → 发货」必须顺序处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @startuml title "Order Event Ordering by Order ID" participant "Order Service" as OS participant "Kafka" as K participant "Order Processor" as OP participant "Inventory Service" as INV participant "Shipping Service" as SHIP OS -> K : ① publish OrderCreated {order_id=123} OS -> K : ② publish OrderPaid {order_id=123} OS -> K : ③ publish OrderShipped {order_id=123} note over OS, K : key=order_id,所有事件路由同一 partition K -> OP : ④ OrderCreated (offset 0) OP -> OP : ⑤ 处理创建 K -> OP : ⑥ OrderPaid (offset 1) OP -> OP : ⑦ 处理支付 OP -> INV : ⑧ reserve stock K -> OP : ⑨ OrderShipped (offset 2) OP -> OP : ⑩ 处理发货 OP -> SHIP : ⑪ create shipment note right of OP 单 partition + 单 consumer 保证顺序处理 但无法并行(同 order) end note @enduml
关键 :key = order_id 让所有同一订单的事件路由到同一 partition。partition 内单 consumer 串行处理 = 顺序 。
DLQ 设计模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @startuml title "DLQ Replay Pattern" queue "Main Queue" as MQ queue "DLQ" as DLQ participant "Consumer" as C participant "DLQ Replayer" as Replay MQ -> C : ① consume C -> C : ② 业务失败 (重试 3 次) C -> MQ : ③ NACK (requeue=false) MQ -> DLQ : ④ 转发到 DLQ (带 reason header) note right of DLQ DLQ 消息: - x-death header 记录失败原因 - original-routing-key - timestamp end note Replay -> DLQ : ⑤ 定时扫描 DLQ Replay -> MQ : ⑥ replay 回主队列 (修复后) @enduml
DLQ 消费策略 :
告警 — DLQ 有消息立即报警
分析 — 人工分析失败原因
修复 + replay — 修好代码后回放
永久归档 — S3 长期存储
实战踩坑
Kafka partition 数固定后不能减少 — 只能增加。生产前评估未来量 。
RabbitMQ 单 queue 顺序 — 多 consumer 抢同一 queue,顺序破坏。用 consistent hash exchange 分配 。
Consumer group 重平衡 — 加实例时短暂 pause(几秒)。生产用 CooperativeStickyAssignor 减少 rebalance 。
消息体过大 — Kafka 默认 1MB,超过拒绝。大消息存 S3,Kafka 存引用 URL 。
Consumer 没提交 offset — 重启后重复消费。业务必须幂等 。
DLQ 没人看 — DLQ 消息堆积,数据丢失 。监控 DLQ depth + 告警 。
Kafka 磁盘满 — retention.ms 过期才删,不够快。设 log.retention.bytes 限制 。
RocketMQ 主从同步延迟 — 异步复制丢消息。用 SYNC_MASTER + 同步刷盘 。
消息时间戳不准确 — Kafka 消息 timestamp 是 producer 写入时间。事件时间戳另存字段 。
决策树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 要做什么? ├─ 日志流 / 大数据管道 → Kafka ├─ 复杂路由 / RPC 风格 → RabbitMQ ├─ 电商事务一致性 → RocketMQ ├─ 简单异步任务 → Redis Streams / Postgres LISTEN └─ IoT / 大量小消息 → MQTT / NATS 顺序要求? ├─ 全局顺序 → 单 partition + 单 consumer (吞吐低) ├─ 同 key 顺序 → key hash partition └─ 无顺序 → 多 partition 并行 消息量? ├─ < 1k msg/s → 任何 MQ ├─ 1k-100k msg/s → Kafka / RocketMQ └─ > 100k msg/s → Kafka + 多 partition + 分片 事务要求? ├─ 弱(可丢失) → 任何 MQ ├─ 不丢不重 → Kafka EOS / RocketMQ 事务 └─ 业务幂等 + 至少一次 → 任何 MQ + 幂等消费
最小起步 :Redis Streams(简单)或 RabbitMQ(单 Docker)。中等规模 :Kafka 单 broker 起步,生产用 3 broker 集群 。高规模 :Kafka + 分区 + 监控 + DLQ + 自动扩容。
记住 :消息队列是异步解耦工具 ,不是万能胶水。简单任务用同步调用 ;跨服务异步 / 流量削峰 / 事件流 才用 MQ。用错场景,反而引入复杂度 。