首页 / 知识库 / python服务端进阶 / 消息队列入门

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 / 优化消费 / 监控告警;削峰场景下短期堆积是正常的
消息顺序单队列单消费者才有序;强顺序需求要专门设计,多数业务无需

教程结语

到这里,消息队列的系统架构和核心思路你已经拿下了:

  1. 为什么用——异步、解耦、削峰(01、03)
  2. 怎么转——生产者 / Broker / 消费者 / 确认机制(02)
  3. 用哪个——业务用 RabbitMQ,大数据用 Kafka,轻量用 Redis(04)
  4. RabbitMQ 怎么工作——交换机路由到队列(05)
  5. 怎么接进 FastAPI——三个独立角色,消费者是常驻进程(06)
  6. 工程上要注意什么——可靠性三问:丢、重、败(本章)

下一步建议:用 Docker 跑一个 rabbitmq:3-management 容器,照着 第 06 章 的骨架,用 pika 写一个最小的”生产者接口 + worker 消费者”,亲手把一条消息从 FastAPI 发出去、在 worker 里收到。跑通这一遍,所有概念就都活了。


上一章 ← 06 - FastAPI + RabbitMQ 的整合架构思路 | 返回 → README 目录