能实现消息不丢,但需同时满足四点:队列与交换器声明为durable=true、消息设deliverymode=amqp.persistent、开启channel.confirm并监听notifypublish、消费时autoack=false且手动ack。

用 github.com/streadway/amqp 能做到消息不丢,但框架本身不自动开启任何持久化或确认机制——漏配一个参数,消息就可能永远消失。
QueueDeclare 必须设 durable = true
队列非持久化是生产环境最常踩的坑:RabbitMQ 重启后,channel.QueueDeclare("task_queue", false, ...) 声明的队列直接消失,新消息无处可去,旧消息(哪怕设了持久化)也因队列不存在而被丢弃。
-
QueueDeclare第二个参数必须为true,否则队列只存内存,重启即空 - 如果队列已存在且是
durable = false,再次用true声明会报错ChannelError: PRECONDITION_FAILED,得先手动删队列或换名 - 交换器(Exchange)也要显式声明为
durable = true,否则即使队列持久,路由失败也会导致消息被丢弃(AMQP 规范要求)
消息体必须设 DeliveryMode: amqp.Persistent
只设队列持久、不设消息持久,等于把信封放进保险柜,但信纸是易燃纸——RabbitMQ 重启时,未落盘的消息全丢。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
amqp.Publishing{DeliveryMode: amqp.Persistent}是硬性要求,不能省;amqp.Transient(默认值)只存内存 -
DeliveryMode是整数字段,amqp.Persistent值为2,写2也能用,但语义不清,容易误传为1 - 注意:若队列非持久,即使消息设了
Persistent,broker 也不会落盘,该参数会被静默忽略
必须开启 channel.Confirm(false) 并监听 NotifyPublish
ch.Publish() 返回 nil ≠ 消息进了队列。网络中断、路由失败、磁盘满等场景下,消息可能根本没进 broker,但你的代码已经“以为发成功”了。
-
ch.Confirm(false)必须在ch.Publish()之前调用,否则所有 publish 都不会触发确认回调 - 用
ch.NotifyPublish(make(chan amqp.Confirmation, 1))接收 ack/nack;缓冲区太小(如cap=0)会导致 goroutine 阻塞,进而卡住整个 channel - 每条消息建议带唯一
correlationId(比如uuid.NewString()),否则无法定位哪条失败 - 超时未收到确认(例如 5 秒),应重试或落地到本地 DB,而不是静默丢弃
channel.Consume 必须设 autoAck = false 并手动 Ack
设 autoAck = true 相当于告诉 RabbitMQ:“消息推过去就算你完成了”,哪怕 handler panic、进程被 kill、甚至只是忘了写处理逻辑,消息都永久消失。
-
ch.Consume(q.Name, "", false, false, false, false, nil)第四个参数是autoAck,必须为false - 消费逻辑完成后,**只调一次**
msg.Ack(false);重复调用会触发ChannelError - 别在
defer里写Ack——panic 时 defer 仍执行,造成误确认 - 失败时用
msg.Nack(false, false)(不重入队)或msg.Nack(false, true)(重入队),避免死循环消费坏消息
真正难的不是写对这四点,而是它们必须同时生效:队列没持久,消息持久也没用;开了 Confirm 却没监听 NotifyPublish,等于没开;手动 Ack 了但 channel 缓冲区溢出,delivery 事件被丢,goroutine 卡住——这些组合问题在线上往往表现为“偶尔丢几条”,极难复现和定位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










