07 - 可靠性与常见坑(思路层面)
消息队列一旦上生产,就绕不开几个灵魂拷问:消息会丢吗?会被重复处理吗?处理失败了怎么办? 这一章只讲思路和应对方向,让你心里有数,不陷进参数细节。
7.1 消息会丢吗?——三个环节,三道保险
一条消息从生产到消费,有三个可能丢的环节。RabbitMQ 对每个环节都有对应的保险:
生产者 ──①──▶ Broker ──②──▶ 队列(存着) ──③──▶ 消费者
↑ ↑ ↑
环节① 环节② 环节③
发出去没到? Broker 挂了没了? 消费者没处理好?
| 环节 | 风险 | 保险机制 | 一句话理解 |
|---|---|---|---|
| ① 生产者→Broker | 消息发出去但 Broker 没收到 | 发布确认(Publisher Confirm) | Broker 收到后回执一下,没回执就重发 |
| ② Broker 存储 | Broker 重启,内存里的消息没了 | 持久化(durable + persistent) | 把队列和消息落到磁盘,重启还在 |
| ③ Broker→消费者 | 消费者拿到消息但处理途中崩了 | 消费确认(ACK) | 处理成功才 ACK,没 ACK 就重新投递 |
核心思想:每一步都”确认了才算数”。要想”消息绝对不丢”,这三道保险要一起开。代价是性能会下降(落磁盘、等回执都要时间),所以要根据业务重要性权衡——发欢迎邮件丢一条无所谓,涉及钱的就要全开。
7.2 消息会重复吗?——几乎一定会,靠”幂等”兜底
会。而且要接受”消息可能被重复处理”这个现实。 最典型的场景:
消费者处理成功了,正准备发 ACK……突然网络抖动 / 消费者崩了
│
▼
Broker 没收到 ACK,认为这条"没处理成功"
│
▼
Broker 把这条消息【重新投递】给另一个消费者
│
▼
结果:同一条消息被处理了两次!
解决思路不是”消灭重复”(做不到),而是让”重复处理”不产生坏结果,这叫幂等:
幂等:同一个操作执行一次和执行多次,结果一样。
怎么做到幂等?常见几招:
- 加唯一标识 + 去重表:每条消息带一个唯一 ID,处理前先查”这个 ID 处理过没”,处理过就直接跳过。
- 用数据库唯一约束:比如”同一订单只能插入一条支付记录”,重复插入会被数据库拦下。
- 操作本身天然幂等:比如”把状态设为已完成”,设几次都一样(而”余额 +100”就不幂等,要小心)。
记住这句话:不要假设消息只会来一次。设计消费者时,默认它可能收到重复消息,用幂等来兜底。
7.3 处理失败了怎么办?——重试与死信队列
消费者处理消息时报错了(比如邮件服务器暂时连不上),怎么办?
思路一:重试
告诉 Broker “这条我没处理成功”(不 ACK 或 nack),让它重新投递,过会儿再试。适合”临时性故障”(网络抖动、下游短暂不可用)。
但要小心无限重试:如果是消息本身有问题(比如格式错误、数据非法),它永远处理不成功,一直重投会卡死队列、疯狂刷错误日志。
思路二:死信队列(DLQ, Dead Letter Queue)
给”怎么都处理不了”的消息一个归宿:重试几次仍失败,就把它丢进一个专门的”死信队列”,不再打扰正常流程。之后人工排查或单独处理。
[正常队列] ──▶ 消费者处理
│
失败重试 N 次仍不行
│
▼
[死信队列 DLQ] ← "问题消息"进这里冷静一下
│
▼
人工排查 / 告警 / 单独补偿
死信队列的价值:把”坏消息”隔离出去,既不丢弃(还留着可排查),又不让它堵住正常业务。生产系统几乎都会配。
7.4 消息堆积怎么办?——盯住消费速度
如果生产得比消费得快,消息就会在队列里越堆越多(堆积)。RabbitMQ 管理后台(:15672)能直观看到队列长度在涨。
应对思路:
消息堆积
│
├─ 消费太慢? → 加 worker(多起几个消费者进程分摊)
│ → 优化消费逻辑(比如批量处理、异步 IO)
│
├─ 生产太猛? → 上游限流,或本来就是削峰的正常现象(高峰过了自然消化)
│
└─ 消费者挂了? → 监控告警,及时拉起
堆积本身不全是坏事——第 03 章 的”削峰”就是故意让它先堆着。要区分”正常缓冲”和”消费者出故障导致的异常堆积”,靠监控来判断。
7.5 消息有顺序吗?
- 单个队列 + 单个消费者:消息是先进先出(FIFO),有序。
- 多个消费者分摊同一队列:消息被并行处理,顺序无法保证。
如果业务强依赖顺序(比如”先创建订单,再支付订单”这两条消息不能乱),要么用单消费者,要么把”有顺序要求的消息”路由到同一个队列串行处理。大多数业务其实不需要严格全局有序,别过度设计。
7.6 上生产前的思路清单
引入 RabbitMQ 时,脑子里过一遍这几个问题就够了:
- 这条消息重要吗?要不要开持久化 + 双向确认(不重要就别开,省性能)?
- 消费者做到幂等了吗(默认消息可能重复)?
- 处理失败怎么办,配了死信队列吗?
- 有没有监控队列堆积和消费者存活?
- 业务需要顺序吗?需要的话怎么保证?
本章小结
| 问题 | 应对思路 |
|---|---|
| 消息会丢 | 三道保险:发布确认 + 持久化 + 消费 ACK,按重要性决定开哪些 |
| 消息重复 | 无法根除,靠幂等兜底(唯一 ID 去重、唯一约束、天然幂等操作) |
| 处理失败 | 重试(临时故障)+ 死信队列(隔离坏消息,避免堵塞) |
| 消息堆积 | 加 worker / 优化消费 / 监控告警;削峰场景下短期堆积是正常的 |
| 消息顺序 | 单队列单消费者才有序;强顺序需求要专门设计,多数业务无需 |
教程结语
到这里,消息队列的系统架构和核心思路你已经拿下了:
- 为什么用——异步、解耦、削峰(01、03)
- 怎么转——生产者 / Broker / 消费者 / 确认机制(02)
- 用哪个——业务用 RabbitMQ,大数据用 Kafka,轻量用 Redis(04)
- RabbitMQ 怎么工作——交换机路由到队列(05)
- 怎么接进 FastAPI——三个独立角色,消费者是常驻进程(06)
- 工程上要注意什么——可靠性三问:丢、重、败(本章)
下一步建议:用 Docker 跑一个
rabbitmq:3-management容器,照着 第 06 章 的骨架,用 pika 写一个最小的”生产者接口 + worker 消费者”,亲手把一条消息从 FastAPI 发出去、在 worker 里收到。跑通这一遍,所有概念就都活了。
上一章 ← 06 - FastAPI + RabbitMQ 的整合架构思路 | 返回 → README 目录