消息队列架构:Kafka / RabbitMQ / RocketMQ 拓扑与顺序保证

puml.online

消息队列是分布式系统的粘合剂——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 三种实现:

  1. 幂等 Producer — ProducerId 去重,不能跨 producer
  2. 事务 API — 跨 partition + offset 原子写
  3. 读-处理-写 — 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

# 输出:
# GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
# inventory-group orders 0 1000 1500 500
# inventory-group orders 1 800 850 50

消息顺序实战

场景:同一订单的「创建 → 支付 → 发货」必须顺序处理

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。用错场景,反而引入复杂度

  • 标题: 消息队列架构:Kafka / RabbitMQ / RocketMQ 拓扑与顺序保证
  • 作者: puml.online
  • 创建于 : 2026-07-30 18:00:00
  • 更新于 : 2026-08-14 21:34:29
  • 链接: https://puml.online/blog/plantuml-message-queue-kafka/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。