消息队列总结:RabbitMQ、Kafka、RocketMQ 与 Redis MQ 选型对比与实践避坑
- 约 19 分钟读完 - 全文 7411 字消息队列总结:RabbitMQ、Kafka、RocketMQ 与 Redis MQ 选型对比与实践避坑
消息队列是分布式系统里最常见的「异步管道」。这篇笔记把 RabbitMQ、Kafka、RocketMQ 和 Redis MQ 放在一起比较,重点回答:什么时候该用哪个?推和拉到底怎么选?长连接有什么影响?以及实际使用中的避坑和异常处理经验。
一、为什么需要消息队列?
在没有 MQ 的系统中,A 服务调用 B 服务通常是同步的:
A ──同步调用──▶ B ──同步调用──▶ C
等待返回 等待返回链路越长,响应越慢,容错越差。MQ 的核心价值是把「同步依赖」变成「异步管道」:
A ──发送消息──▶ MQ ──接收消息──▶ B
立即返回 异步处理1.1 MQ 解决的三大问题
- 解耦:生产方只负责发消息,不用关心谁消费、怎么消费。
- 异步:把非核心链路(发短信、记日志、发券)从主流程剥离,降低接口 RT。
- 削峰填谷:突发流量先写入队列,下游按自身能力消费,避免被压垮。
1.2 什么时候必须引入 MQ?
- 上下游处理速度差异大,同步调用会导致阻塞或超时。
- 需要保证某些操作最终执行,但不需要实时返回结果。
- 系统拆分后,多个服务需要感知同一事件。
- 大促/活动场景下,需要缓冲突发流量。
二、四种 MQ 横向对比
RabbitMQ、Kafka、RocketMQ 是主流企业级 MQ,Redis 也能当轻量 MQ 用,但定位不同。
2.1 核心定位
| 维度 | RocketMQ | RabbitMQ | Kafka | Redis MQ |
|---|---|---|---|---|
| 出身与设计目标 | 阿里开源,面向在线交易/金融级消息 | Erlang 实现,面向通用企业消息 | LinkedIn 开源,面向日志采集与流处理 | 内存数据库,轻量级队列/发布订阅 |
| 数据模型 | Topic + Queue,支持 Tag 过滤 | Exchange + Queue,路由灵活 | Topic + Partition,分区有序 | List / Stream / PubSub |
| 默认消费方式 | Push(长轮询封装)+ Pull | Push(AMQP 推送) | Pull(消费者主动拉取) | Push(PubSub)/ Pull(BLPOP) |
| 顺序消息 | 分片内有序,Sharding Key 控制 | 单 Queue 有序 | Partition 内有序 | 单 Stream/列表有序 |
| 延迟消息 | 原生支持 18 个级别 | 需插件或死信实现 | 不原生支持,需借助外部组件 | 无原生支持 |
| 事务消息 | 原生支持 | 不支持原生事务消息 | 不支持原生事务消息 | 不支持 |
| 消息持久化 | 磁盘持久化,支持同步/异步刷盘 | 磁盘持久化 | 磁盘持久化,顺序写 | 默认内存,可持久化但非强项 |
| 消息轨迹 | 原生支持 | 需额外配置 | 需额外组件 | 无 |
2.2 性能对比
| 维度 | RocketMQ | RabbitMQ | Kafka | Redis MQ |
|---|---|---|---|---|
| 单机吞吐 | 10 万级 TPS | 万级 TPS | 百万级 TPS(批量场景) | 十万级 TPS(内存操作) |
| 延迟 | 毫秒级 | 微秒-毫秒级 | 毫秒级,批量时更高 | 微秒-毫秒级 |
| 消息堆积能力 | 强,适合削峰 | 一般,堆积过多影响性能 | 极强,专为日志设计 | 弱,内存容量受限 |
| 批量效率 | 中等 | 一般 | 极高 | 一般 |
说明:
- Kafka 吞吐数字通常来自批量、压缩、顺序写的日志场景;单条小消息低延迟不如 RocketMQ。
- RabbitMQ 延迟低,但高吞吐和海量堆积不是强项。
- RocketMQ 在「中等吞吐 + 低延迟 + 强堆积」之间取得了较好平衡。
- Redis MQ 性能高,但消息堆积、持久化、事务、延迟消息都不完善,适合轻量、临时场景。
2.3 成本与生态
| 维度 | RocketMQ | RabbitMQ | Kafka | Redis MQ |
|---|---|---|---|---|
| 部署复杂度 | 中等(NameServer + Broker) | 低(单节点即可运行) | 高(ZooKeeper/KRaft + Broker) | 低(Redis 单实例/集群) |
| 运维成本 | 中等,阿里系文档丰富 | 低,但 Erlang 问题排查门槛高 | 高,调优项多 | 低,Redis 团队普遍熟悉 |
| 云服务成熟度 | 阿里云 RocketMQ 成熟 | 各云厂商都有托管版 | 各云厂商 Kafka 托管版成熟 | 云 Redis 托管版成熟 |
| 学习曲线 | 中等 | 低 | 高 | 低 |
| 大数据生态 | 一般,可与 Flink/Spark 对接 | 弱 | 极强,流处理事实标准 | 弱 |
| 语言客户端 | Java 最完善,其他语言有社区版 | 极其丰富 | 极其丰富 | 极其丰富 |
2.4 适用场景推荐
| 场景 | 推荐 | 原因 |
|---|---|---|
| 电商/金融订单、交易消息 | RocketMQ | 延迟低、事务消息、顺序消息、监控完善 |
| 异步任务、分布式 Worker、视频渲染 | RocketMQ | PushConsumer 自动负载均衡,失败重试完善 |
| 传统企业应用、RPC 解耦、路由复杂 | RabbitMQ | 简单易用,Exchange 路由灵活,快速上线 |
| 日志采集、埋点、实时数仓、流处理 | Kafka | 吞吐和生态碾压,分区模型适合流计算 |
| 事件总线、多语言异构系统 | RabbitMQ / Kafka | 客户端生态更丰富 |
| 需要事务消息且不想自己实现 | RocketMQ | 原生事务消息是差异化能力 |
| 轻量异步通知、缓存队列、临时任务 | Redis MQ | 部署简单、延迟低,但不要把核心交易交给它 |
| 已有 Redis 基础设施,不想引入新组件 | Redis MQ | 作为过渡方案或辅助队列可用 |
2.5 一句话选型
可靠交易、事务消息、顺序消息、在线业务 → RocketMQ
快速搭建、路由灵活、团队规模小 → RabbitMQ
日志/流处理/大数据管道、吞吐优先 → Kafka
轻量通知、临时队列、已有 Redis 生态 → Redis MQ很多大厂内部是「Kafka 做日志流 + RocketMQ 做业务消息 + RabbitMQ 做轻量任务 + Redis 做缓存/通知」组合使用,不是单选题。
三、底层架构与核心差异
3.1 四种 MQ 架构对比
flowchart LR
subgraph RocketMQ
P1[Producer] --> NS[NameServer]
NS --> B1[Broker Master]
B1 --> B2[Broker Slave]
B1 --> C1[Consumer]
end
subgraph Kafka
P2[Producer] --> KZ[ZooKeeper / KRaft]
KZ --> KB1[Broker 1]
KZ --> KB2[Broker 2]
KB1 --> C2[Consumer]
end
subgraph RabbitMQ
P3[Producer] --> EX[Exchange]
EX --> Q1[Queue A]
EX --> Q2[Queue B]
Q1 --> C3[Consumer A]
Q2 --> C4[Consumer B]
end
subgraph RedisMQ
P4[Producer] --> R[(Redis)]
R --> C5[Consumer BLPOP]
R --> C6[Subscriber]
end
3.2 存储模型
| 产品 | 存储结构 | 核心特点 |
|---|---|---|
| RocketMQ | CommitLog + ConsumeQueue | 所有 Topic 的消息顺序写入同一个 CommitLog,再用 ConsumeQueue 存每个 Queue 的偏移索引。顺序写磁盘,I/O 效率高。 |
| Kafka | Partition Segment + Index | 每个 Partition 是独立日志目录,顺序写入 Segment,配合稀疏索引。Partition 间物理隔离。 |
| RabbitMQ | Queue 独立存储 | 每个 Queue 维护独立索引和存储,Queue 越多元数据开销越大。 |
| Redis MQ | 内存 List / Stream | 基于内存操作,速度快,但容量受内存限制,持久化可靠性不如专业 MQ。 |
flowchart TB
subgraph RocketMQ_Storage["RocketMQ 存储模型"]
CL[CommitLog<br/>所有消息顺序写]
CQ1[ConsumeQueue A<br/>偏移索引]
CQ2[ConsumeQueue B<br/>偏移索引]
CL --> CQ1
CL --> CQ2
end
subgraph Kafka_Storage["Kafka 存储模型"]
KP1[TopicA-P0 Log<br/>独立 Partition]
KP2[TopicA-P1 Log<br/>独立 Partition]
end
subgraph RabbitMQ_Storage["RabbitMQ 存储模型"]
MQ1[Queue A<br/>独立索引]
MQ2[Queue B<br/>独立索引]
end
3.3 路由与元数据管理
| 产品 | 注册/路由中心 | 设计哲学 |
|---|---|---|
| RocketMQ | NameServer | 无状态集群,Broker 主动上报路由,Producer/Consumer 定时拉取。最终一致,轻量。 |
| Kafka | ZooKeeper / KRaft | 老版本依赖 ZooKeeper 存元数据、选 Controller;新版本用 KRaft 自洽协议。强一致要求更高。 |
| RabbitMQ | 内置 Mnesia 数据库 | 集群节点共享元数据,Erlang 进程模型天然分布式,但集群扩展性相对有限。 |
| Redis MQ | 无 | 依赖 Redis 自身的 Cluster/哨兵做高可用,没有独立路由层。 |
3.4 复制与高可用
| 产品 | 复制方式 | 特点 |
|---|---|---|
| RocketMQ | Master-Slave 同步/异步复制 | 同步复制保证不丢消息,异步复制性能更高。 |
| Kafka | Partition 多副本 + ISR 机制 | 自动 Failover,成熟度高。 |
| RabbitMQ | 普通队列 + 镜像队列 | 普通队列只存在于一个节点;镜像队列复制到多个节点但吞吐下降。 |
| Redis MQ | 主从 + 哨兵 / Cluster | Redis 自身的高可用机制,不是为消息队列专门设计。 |
四、推、拉模式与长连接
4.1 Push vs Pull 对比
| 维度 | Push(推) | Pull(拉) |
|---|---|---|
| 控制权 | 在 Broker | 在 Consumer |
| 实时性 | 高,Broker 有消息就发 | 依赖拉取间隔,实时性可调 |
| 背压能力 | 较弱,Consumer 处理慢会堆积 | 强,Consumer 自己控制速率 |
| 实现复杂度 | 低,业务写回调即可 | 高,需自管 offset、拉取循环 |
| 适用场景 | 实时性要求高、消费轻量 | 批量处理、消费重、需要控速 |
4.2 四种 MQ 的推拉实现
| 产品 | 消费方式 | 是否长连接 | 说明 |
|---|---|---|---|
| RocketMQ | 长轮询 Pull 封装成 Push | 是 | Consumer 与 Broker 保持长连接,Broker hold 请求 5 秒左右,有新消息立即返回。 |
| Kafka | 消费者主动 Pull | 否(可保持连接但非必需) | Consumer 自己决定何时拉、拉多少,灵活性最高。 |
| RabbitMQ | Broker 真 Push(AMQP) | 是 | Channel 长连接,Broker 把消息塞给 Consumer,处理完 ACK。 |
| Redis MQ | PubSub 推 / BLPOP 拉 | 是 | PubSub 是长连接推送;BLPOP 是阻塞拉取,也算长连接。 |
4.3 长连接的影响
flowchart LR
subgraph 长连接优势
A1[低延迟]
A2[实时推送]
A3[减少建连开销]
end
subgraph 长连接代价
B1[连接数消耗大]
B2[需要心跳保活]
B3[网络断开感知延迟]
B4[Consumer 慢会拖垮 Broker]
end
长连接的好处:
- 消息到达可以立刻推送给 Consumer,延迟低。
- 避免频繁建立/断开 TCP 连接的开销。
- 适合在线业务、实时通知类场景。
长连接的代价:
- Broker 需要维护大量连接,连接数成为重要资源。
- 必须有心跳机制,否则无法及时发现 Consumer 宕机。
- Consumer 处理慢时,消息会堆积在 Broker 内存或磁盘,容易把 Broker 压垮。
- 网络闪断后需要重连、重平衡,可能带来消费抖动。
4.4 选择原则
flowchart TD
A[选择消费模式] --> B{实时性要求高?}
B -->|是| C{消费逻辑轻量?}
B -->|否| D[Pull 模式]
C -->|是| E[Push 模式]
C -->|否| F[Pull 模式 或 限流 Push]
D --> G[批量处理/ETL/数据同步]
E --> H[在线业务/实时通知]
F --> I[重任务/视频渲染/大数据]
五、使用注意点与避坑指南
5.1 RocketMQ 避坑
| 坑 | 现象 | 解决 |
|---|---|---|
| 消费组订阅不一致 | 同一个 ConsumerGroup 订阅不同 Topic/Tag,重平衡混乱 | 一个 ConsumerGroup 只订阅一个 Topic + 一套 Tag |
| Tag 用得太细 | 管理混乱,消费者过滤复杂 | Topic 按业务域划分,Tag 做粗粒度过滤 |
| 消息体过大 | 超过 4MB 默认限制,发送失败 | 消息体只传 ID 和必要字段,完整数据从 DB/OSS 拉取 |
| 顺序消息误用 | 全局顺序严重拖垮吞吐 | 只在真正需要局部顺序的场景用 Sharding Key |
| NameServer 路由延迟 | Broker 扩缩容后发送到已下线节点 | 理解最终一致性,扩缩容留足等待时间 |
| 自动创建 Topic | 生产环境订阅失败或路由错误 | 提前创建 Topic 和 ConsumerGroup |
5.2 Kafka 避坑
| 坑 | 现象 | 解决 |
|---|---|---|
| Partition 数设置不当 | 太少并行度不够,太多元数据压力大 | 根据吞吐和 Consumer 数合理规划 |
| 消费者 Rebalance 频繁 | 消费停滞,组员加入/退出频繁 | 避免频繁扩缩容,调整 session.timeout.ms |
| Offset 提交策略不当 | 重复消费或消息丢失 | 重要消息手动提交,处理完再 commit |
| Producer acks=0/1 | 丢消息 | 关键业务用 acks=all |
| 单 Partition 热点 | 某个 Partition 消费滞后 | 合理设计 Key,避免 Key 倾斜 |
5.3 RabbitMQ 避坑
| 坑 | 现象 | 解决 |
|---|---|---|
| 镜像队列性能下降 | 开镜像后吞吐明显降低 | 只对关键队列开镜像 |
| 队列堆积过多 | Broker 内存/磁盘告警,性能下降 | 控制生产速度,及时扩容 Consumer |
| ACK 模式用错 | 消息丢失或重复 | 手动 ACK,处理完再确认 |
| Exchange 路由复杂 | 消息去向难以追踪 | 规范命名,做好监控 |
| Connection/Channel 滥用 | 连接数耗尽 | Channel 复用,避免每个消息新建连接 |
5.4 Redis MQ 避坑
| 坑 | 现象 | 解决 |
|---|---|---|
| PubSub 消息丢失 | Consumer 断线期间的消息收不到 | 重要消息用 Stream 或专业 MQ |
| List 做队列无 ACK | Consumer 处理失败消息丢失 | 用 Stream 的 ACK 机制 |
| 内存不足 | Redis OOM | 设置合理过期时间,控制队列长度 |
| 把 Redis 当核心 MQ | 数据丢失、无事务、无延迟消息 | 核心交易链路用 RocketMQ/Kafka |
5.5 通用最佳实践
- 消息体尽量小:传 ID 不传大对象。
- 幂等设计:所有 Consumer 都要做幂等,MQ 只保证至少一次投递。
- 监控到位:堆积量、消费 TPS、延迟、死信队列、Broker 资源。
- 版本一致:服务端和客户端版本不要差距太大。
- 提前创建资源:生产环境不要依赖自动创建 Topic/Queue。
- 区分可重试与不可重试异常:网络抖动可以重试,参数错误直接进死信。
六、异常场景处理(分 MQ 说明)
下面列出的异常场景在 RocketMQ、RabbitMQ、Kafka、Redis MQ 中都会遇到,但每种 MQ 提供的机制和最佳实践不同。这一节把四种 MQ 放在一起对比说明。
flowchart LR
P[Producer 发送层] -->|发送失败/超时| B[Broker 存储层]
B -->|刷盘/复制失败| C[Consumer 消费层]
C -->|重复/失败/卡住| Biz[业务异常兜底]
6.1 消息丢失:各 MQ 如何防范
消息丢失通常发生在三个环节:Producer 发送失败、Broker 存储失败、Consumer 消费失败未 ACK。四种 MQ 的防范方式如下:
| 环节 | RocketMQ | RabbitMQ | Kafka | Redis MQ |
|---|---|---|---|---|
| Producer 发送 | 同步发送 SYNC + 失败重试;避免 ONEWAY |
使用 confirm 模式等待 Broker ACK;开启 Publisher Confirms |
设置 acks=all + 重试;避免 acks=0/1 |
无发送确认机制,调用成功不代表持久化成功;重要消息不要用 Redis PubSub |
| Broker 存储 | 同步刷盘 SYNC_FLUSH + 同步复制 SYNC_MASTER |
队列持久化 + 镜像队列复制;消息标记 persistent |
replication.factor >= 3 + min.insync.replicas 控制 |
AOF/ RDB 持久化,但默认异步;主从切换可能丢数据 |
| Consumer 消费 | 先处理业务再返回 CONSUME_SUCCESS |
手动 ACK,处理完再 basic.ack |
处理完再 commit offset | Stream 用 XACK 确认;List/ PubSub 无 ACK |
| 兜底 | 业务对账 + 补偿表 | 业务对账 + 补偿表 | 业务对账 + 补偿表 | 不建议做核心兜底,Redis 本身不是可靠 MQ |
关键提醒:
- RocketMQ:
SYNC_MASTER+SYNC_FLUSH配置最安全,但性能最低;在线交易通常用SYNC_MASTER+ASYNC_FLUSH折中。 - RabbitMQ:默认自动 ACK 最容易丢消息,生产环境一定要改手动 ACK。
- Kafka:
acks=all配合min.insync.replicas才能保证写入 ISR 多数副本;只配置acks=all不够。 - Redis MQ:Redis 本身不是为消息可靠性设计的,不要把核心交易消息的防丢失交给 Redis。
6.2 消息重复:各 MQ 的投递语义与幂等
四种 MQ 大多只保证「至少一次投递」(at-least-once),重复消费要靠业务层幂等。
| MQ | 默认投递语义 | 重复来源 | 推荐幂等方案 |
|---|---|---|---|
| RocketMQ | At-least-once | 网络超时重发、Consumer 重启、Broker 重试 | 消息 keys 做唯一业务 Key + 数据库唯一索引 |
| RabbitMQ | At-least-once | 连接断开、ACK 未到达 Broker、Consumer 重启 | 消息 message_id 或业务 Key + 幂等表 |
| Kafka | At-least-once | Consumer Rebalance、Offset 提交失败、Producer 重试 | 业务 Key 去重;Kafka 幂等 Producer 只能去重单会话内重复 |
| Redis MQ | 不保证 | PubSub 不持久化,断线即丢;Stream 相对可靠但无原生重试 | 业务侧用 Redis SETNX 或数据库唯一索引 |
# 通用幂等伪代码,适用于四种 MQ
def consume(msg):
biz_key = extract_biz_key(msg) # RocketMQ keys / RabbitMQ message_id / Kafka key
try:
idempotent_record.insert(biz_key=biz_key, status="processing")
except UniqueViolation:
return SUCCESS # 已处理过,直接成功
try:
do_business(msg)
idempotent_record.update(biz_key, status="done")
return SUCCESS
except RetryableException:
idempotent_record.update(biz_key, status="failed")
return RETRY
except NonRetryableException:
idempotent_record.update(biz_key, status="failed")
return DEAD_LETTER6.3 消费失败:重试与死信机制
消费失败后,不同 MQ 的重试和死信(DLQ)机制差异很大:
| MQ | 重试机制 | 死信队列 | 注意事项 |
|---|---|---|---|
| RocketMQ | 默认最多 16 次,间隔阶梯递增(10s、30s、1m…) | 自动进入 %DLQ%consumer_group |
可区分 RECONSUME_LATER 重试和直接成功;重试次数可在 Broker 配置 |
| RabbitMQ | 无内置重试,需业务侧或插件实现;常用 TTL + 死信交换器(DLX)做延迟重试 | 通过 x-dead-letter-exchange 配置 DLX/DLQ |
手动 ACK 失败或 NACK + requeue=false 进入死信 |
| Kafka | 无内置重试次数,失败时 Consumer 自己决定是否重试或跳过 | 无原生 DLQ,需业务侧或 Kafka Connect 实现 | 常见做法:失败消息写入专用 Error Topic,由补偿服务处理 |
| Redis MQ | 无内置重试 | 无原生 DLQ | Stream 可配合 Consumer Group 的 Pending List 人工处理;List/ PubSub 无重试能力 |
flowchart TD
A[Consumer 处理失败] --> B{MQ 类型}
B -->|RocketMQ| C[返回 RECONSUME_LATER]
B -->|RabbitMQ| D[NACK + requeue=false 进 DLX]
B -->|Kafka| E[写入 Error Topic 或暂停消费]
B -->|Redis Stream| F[XCLAIM 或人工处理 Pending]
C --> G{重试超限?}
G -->|是| H[进入 DLQ]
G -->|否| C
D --> H
E --> I[补偿服务消费 Error Topic]
F --> J[业务侧补偿]
可重试异常:网络超时、依赖服务短暂不可用、数据库连接池耗尽。
不可重试异常:参数非法、依赖返回 4xx、业务规则冲突、数据不存在。
6.4 业务异常处理
业务异常(如参数错误、状态冲突、规则校验失败)和系统异常(网络、DB、依赖故障)要分开处理。
flowchart TD
A[Consumer 捕获异常] --> B{异常类型}
B -->|系统异常<br/>网络/DB/依赖| C[MQ 重试]
B -->|业务异常<br/>参数/规则/状态冲突| D[记录失败明细]
D --> E{能否修复?}
E -->|能| F[人工补偿/修复后重投]
E -->|不能| G[进死信队列或丢弃]
C --> H{重试成功?}
H -->|否| I[进入死信/Error Topic]
| MQ | 业务异常处理建议 |
|---|---|
| RocketMQ | 捕获业务异常后,直接返回 CONSUME_SUCCESS 并记录失败原因,避免无意义重试;或主动放入自定义补偿 Topic。 |
| RabbitMQ | 业务异常时 basic.nack(requeue=false) 直接进死信,不要在 RabbitMQ 里反复重试业务错误。 |
| Kafka | 业务异常写入 Error Topic,由补偿服务按业务规则处理;Consumer 继续消费后续消息,避免阻塞 Partition。 |
| Redis MQ | Redis 不适合复杂异常处理;业务异常建议直接记录到数据库或日志,人工介入。 |
业务异常兜底策略:
- 记录失败明细:消息内容、异常原因、时间、Consumer 实例、业务 Key。
- 人工补偿平台:按业务 Key 重投、跳过、标记已处理。
- 状态机兜底:订单等核心业务要有最终一致性校验。
- 告警分级:死信队列增长、消费停滞、重试频繁分级告警。
- 不要无限重试业务异常:参数错误重试 16 次没有意义。
6.5 消息卡住/消费停滞
消费停滞是四种 MQ 都会遇到的异常,但原因和处理方式各有侧重:
| MQ | 常见原因 | 处理手段 | 监控指标 |
|---|---|---|---|
| RocketMQ | Consumer 实例少、单条处理慢、Consumer 宕机、下游依赖慢 | 扩容 Consumer(不超过 Queue 数)、优化消费逻辑、限流、降级 | ConsumeProgress 堆积量、消费 TPS |
| RabbitMQ | 队列堆积导致内存/磁盘告警、Consumer 连接断开、Channel 阻塞 | 扩容 Consumer、控制生产速度、启用队列长度限制 | Queue 深度、内存使用率、磁盘告警 |
| Kafka | Partition 消费滞后、Consumer Rebalance 频繁、单 Partition 热点 | 增加 Partition、优化 Key 设计、调整 session.timeout.ms |
Consumer Lag、Rebalance 频率 |
| Redis MQ | 内存不足、Consumer 断线(PubSub 丢消息)、处理慢 | 控制队列长度、设置过期时间、用 Stream 替代 PubSub | Redis 内存、Pending List 长度 |
flowchart LR
A[消息堆积] --> B{原因}
B -->|Consumer 少| C[扩容 Consumer]
B -->|单条处理慢| D[优化消费逻辑/异步化]
B -->|Consumer 宕机| E[心跳超时触发重平衡]
B -->|下游依赖慢| F[限流/降级/异步处理]
B -->|Partition/Queue 热点| G[调整 Key 或增加分区]
6.6 顺序消息异常
顺序消息一旦某条处理卡住,后续消息都会被阻塞,四种 MQ 的处理方式不同:
| MQ | 顺序模型 | 异常影响 | 处理建议 |
|---|---|---|---|
| RocketMQ | Sharding Key 路由到同一 MessageQueue,单线程消费 | 一条失败会阻塞整个 Queue 的后续消息 | 设置消费超时;失败次数过多时考虑跳过或转移;业务上保证单条处理轻量 |
| RabbitMQ | 单 Queue 天然有序,单 Consumer 单线程消费 | 一个 Consumer 卡住会阻塞整个 Queue | 控制单条处理时间;必要时拆分多个 Queue 做局部顺序 |
| Kafka | 同一 Partition 内有序,单 Consumer 消费 Partition | 一条消息处理慢会阻塞整个 Partition | 合理设计 Key,避免单个 Partition 成为热点;业务异常时写入 Error Topic 避免阻塞 |
| Redis MQ | Stream/ List 单节点内有序 | 单 Consumer 卡住会阻塞后续消费 | 不建议用 Redis 做强顺序消息;处理异常时直接记录并人工处理 |
flowchart LR
A[Queue/Partition 内顺序消费] --> B[消息 1 处理失败或超慢]
B --> C[消息 2 等待]
C --> D[消息 3 等待]
B --> E{如何处理?}
E -->|跳过| F[破坏顺序性]
E -->|重试| G[阻塞后续消息]
E -->|转移| H[进死信/Error Topic]
顺序消息异常处理原则:
- 只在真正需要顺序的场景使用顺序消息。
- 单条消息处理要轻量,避免长耗时阻塞队列。
- 业务上设计「局部顺序」而不是「全局顺序」。
- 顺序消息失败时,优先考虑业务补偿,而不是无限重试或简单跳过。
七、总结
- 选型不是单选题:RocketMQ 适合在线业务,Kafka 适合日志/流处理,RabbitMQ 适合轻量快速落地,Redis MQ 适合轻量辅助场景。
- 推/拉不是对立关系:RocketMQ 的 Push 底层是长轮询 Pull;RabbitMQ 是真 Push;Kafka 是真 Pull。选择看「谁来控制消费节奏」。
- 长连接是双刃剑:实时性好,但连接维护、背压、心跳都要考虑。
- 异常处理要分层:发送层确认、Broker 层持久化、消费层幂等 + 重试 + 死信。
- 业务异常要兜底:区分可重试与不可重试,建立补偿机制和人工介入平台。
- 避坑靠规范:订阅一致、消息体小、顺序消息慎用、监控到位、版本一致。
参考
- RocketMQ 基础科普:推/拉/通知模式、异常处理与高并发实践
- 《RocketMQ 实战与核心原理》
- 《RocketMQ 技术内幕》
- 《Kafka 权威指南》
- 《RabbitMQ 实战指南》
如果对你有帮助,欢迎在评论区交流你在 MQ 选型或异常处理上的经验。