消息不丢失需生产者、Broker、消费者三端确认与持久化全部对齐:生产者须开publisher confirms并等待ack、消费者禁用auto-ack并手动ack/nack、channel合理缓冲且交换器/队列/消息三处均设durable。

消息不丢失不是靠“用了 RabbitMQ”或“开了持久化”就能自动实现的,而是生产者、Broker、消费者三端确认机制和持久化配置必须全部对齐——漏掉任意一环,高并发下丢消息就是大概率事件。
ch.Publish 后没等 confirm 就算发送成功?
Go 的 ch.Publish 返回 nil 只代表 AMQP 帧写入 socket 成功,不代表消息进了队列。默认情况下 Broker 根本不回 ACK。
- 必须在
ch.Publish前调用ch.Confirm(false),否则所有 publish 都不会触发 confirm 事件 - 每条消息要带唯一
correlationId(比如uuid.NewString()),不然无法区分哪条被 nack - 用
ch.NotifyPublish接收amqp.Confirmation,超时未收到(建议 5s)需重试或落库,不能直接丢弃 - 别在 goroutine 里发完就不管——
ch.Publish是异步的,不监听 confirm 就等于裸发
autoAck: true 是线上最大隐患
设 ch.Consume(q.Name, "", true, false, false, false, nil) 意味着 RabbitMQ 一推送消息就立刻删除,哪怕你的 handler 还没开始执行、甚至进程被 kill -9,消息也永远消失了。
- 必须设为
false:ch.Consume(..., false, ...) - 处理成功后只调一次
d.Ack(false);重复调会触发ChannelError - 失败时明确选
d.Nack(false, false)(丢弃)或d.Nack(false, true)(重入队),后者必须配死信队列防循环 - 别在
for range msgs外写defer d.Ack()——那会把整批消息都误确认
持久化三件套缺一不可
amqp.Persistent 不是万能开关。它只在 Exchange 和 Queue 都声明为 durable: true 的前提下才生效。Broker 重启后,三者任一不 durable,消息就没了。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 声明 Exchange 时必须传
amqp.Table{"durable": true} - 声明 Queue 时
durable参数必须为true,且队列名固定(不能用空字符串或随机名) - 发送消息时
amqp.Publishing{DeliveryMode: amqp.Persistent}才真正写盘 - 验证方法:RabbitMQ 管理界面看 Queue 的 Durability 列是否为
Yes,Message 的 Delivery mode 是否为2
channel 缓冲区不是越大越安全
用 make(chan amqp.Delivery, 1000) 接收消费消息,看似缓冲充足,实则掩盖了真实瓶颈——当 len(ch) == cap(ch) 时,ch.Consume 会阻塞,新消息进不来,但旧消息又卡在 channel 里没处理完,最终导致连接超时、channel 关闭、消息丢失。
- 初始缓冲设 100,上线后监控
len(ch),超过0.8 * cap(ch)就告警 - 每个 worker 处理前打时间戳,结束后打耗时,若 P99 > 500ms,优先优化单条逻辑而非扩 buffer
- 配合
ch.Qos(1, 0, false)限制预取数,防止消息堆积在内存中却未确认 - 别多个消费者共用一个 delivery channel —— 来源混杂时,错误隔离和重试策略全乱套
最常被忽略的是:消息从 ch.Publish 调用到收到 confirm、从 ch.Consume 收到 delivery 到 d.Ack 提交,这两段链路中间没有任何自动兜底。任何一环 panic、超时、网络抖动、goroutine 泄漏,都会断掉确认流。所以不能只盯代码有没有写 Ack,而要盯住整个协作生命周期是否可观察、可中断、可重试。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










