启用 publisher confirms 是 rabbitmq 生产环境可靠性的底线,需显式调用 channel.confirm(),配合 await channel.wait_for_confirms() 或回调处理 ack/nack,避免 fire-and-forget 风险。

用 aio-pika 开启 Publisher Confirms 才算真正可靠
不启用确认机制的 publish 调用,本质上是“发完即忘”——网络断开、Broker 崩溃、磁盘满,消息就彻底蒸发。RabbitMQ 的 Publisher Confirms 不是可选项,而是生产环境的底线。
- 必须在连接后显式调用
channel.confirm(),否则所有publish都走 fire-and-forget 模式 - 不要依赖
basic_publish的返回值:它只表示写入 socket 成功,不代表 Broker 已落盘 - ACK/NACK 是异步到达的,需配合
await channel.wait_for_confirms()或监听回调,不能靠 sleep 等待 - Python 3.12+ 中若使用
asyncio.timeout()包裹发布逻辑,超时后必须主动清理未确认消息,否则会堆积在内存里
retry_task 不能只靠 time.sleep(2 ** i)
指数退避看着合理,但实际线上会出两类典型问题:一是重试压垮下游(比如连续重试触发 500 次订单创建),二是死信队列被绕过(失败消息没进 DLX 就卡在重试循环里)。
- 重试前先检查错误类型:
ConnectionClosedError应立即重连再试,BusinessValidationError则该直接丢弃 - 最大重试次数建议设为 3~5 次,超过后必须调用
channel.basic_nack(delivery_tag=..., requeue=False)推入死信队列 - 避免在重试逻辑里重复序列化消息体——原始
message.body是 bytes,反复json.loads()再dumps()可能引入编码或精度丢失 - 如果用了 Redis 存 pending 状态,重试时要先
GET确认该消息还没被其他实例处理,否则会重复投递
本地事务 + 事件表不是银弹,慎用于高并发写场景
把业务数据和待发事件塞进同一张 MySQL 表,靠定时任务扫表重发,听起来原子性强,但真实压测下常成性能瓶颈。
- 每笔订单创建都额外多一次
INSERT INTO events,QPS 过 500 就可能拖慢主事务响应 - 事件恢复服务的扫描间隔(如 30s)意味着故障恢复有天然延迟,不适合金融类强实时场景
- 若用
SELECT ... FOR UPDATE扫描未发送事件,容易引发行锁争用,尤其当多个恢复实例并行时 - 更稳妥的做法是:核心链路用外部事件系统(如独立部署的 Kafka + 状态机服务),仅低频配置类变更走本地事件表
消费者端的幂等不是加个 if taskProcessed(task_id) 就完事
看似简单的判重逻辑,上线后才发现:数据库查一次、业务处理一次、再存一次标记——三步之间仍存在窗口期,重复消费照样发生。
立即学习“Python免费学习笔记(深入)”;
- 必须用原子操作完成“校验 + 标记”,例如 PostgreSQL 的
INSERT ... ON CONFLICT DO NOTHING,或 Redis 的SET task_id 1 EX 3600 NX - 不要用时间戳或版本号做幂等键——如果消息重发时
task_id相同但 payload 有差异(如金额修正),应允许覆盖处理,而非简单跳过 - 幂等键的生命周期要匹配业务语义:支付类用
order_id + payment_id,日志类用log_id + hash(content),不能全站统一用uuid4() - 如果消费者重启导致内存中的
processed_set清空,靠缓存兜底也不够——Redis 故障时,得有降级方案(如写本地文件 + 定时同步)
可靠性不是某个开关或一层封装,而是从生产者确认、传输重试、存储持久化到消费者幂等的完整闭环。漏掉任意一环,都可能在流量高峰或节点异常时暴露出来。










