rabbitmq 的 autoack: true 是线上最大隐患,因消息刚送达消费者内存即被删除,导致业务未执行就丢失;必须设为 false 并手动 ack/nack,配合持久化、qos、幂等与连接管理。

为什么 RabbitMQ 的 autoAck: true 是线上最大隐患
它让消息在“刚送到消费者内存”那一瞬间就被 RabbitMQ 删掉,哪怕你的业务逻辑还没执行、甚至立刻 panic,消息也永远不会再回来。这是订单重复扣款、积分重复发放最常见根源。
- 必须设为
autoAck: false,并在业务处理成功后显式调用msg.Ack(false) - 若处理失败,用
msg.Nack(false, true)让消息重回队列头部(注意配合死信队列防循环) - 千万别在
for range msgs循环外写defer msg.Ack()——那会把所有消息都确认掉 - 加一层保护:用
context.WithTimeout(ctx, 30*time.Second)包裹整个处理逻辑,超时直接Nack
amqp.Publishing{DeliveryMode: amqp.Persistent} 不生效?检查这三处
消息进内存、Broker 一崩就全丢,不是配置漏了,就是理解错了“持久化”的作用边界。
-
DeliveryMode: amqp.Persistent只保证这条消息写入磁盘,前提是 Exchange 和 Queue 本身也得是durable: true - 声明 Exchange 时漏掉
durable: true?RabbitMQ 重启后绑定关系消失,消息路由失败却静默丢弃 - 生产者发消息时用了空字符串
""当 exchange 名?默认交换机只支持 direct 路由,且 routing key 必须严格等于队列名——极易误配 - 验证方法:RabbitMQ 管理界面看 Queue 的
Durability列是否为Yes,再看 Message 的Delivery mode是否为2
消费者重启后消息堆积?别怪队列,先查连接和 Channel 复用
不是消息太多,而是旧连接没关干净,新消费者抢不到队列消费权,或预取值(QoS)设得太大导致消息卡在内存里不确认。
- 一个服务实例只建 1 个
*amqp.Connection,复用它创建多个*amqp.Channel;频繁Dial()会快速耗尽本地端口 - 启动时调用
channel.Qos(1, 0, false),限制最多 1 条未确认消息,避免批量拉取后崩溃导致整批滞留 - 务必监听连接断开:
conn.NotifyClose(make(chan *amqp.Error, 1)),收到通知后主动重建连接和 channel - 关闭顺序必须是:
ch.Close()→conn.Close(),反着来会导致 channel 泄露,后续Consume()直接 panic
消息体 JSON 序列化为空 {}?90% 是结构体字段没导出
Go 的 json.Marshal 对小写开头字段完全忽略,也不报错,你看到的空对象就是它干的。
立即学习“go语言免费学习笔记(深入)”;
- 结构体字段首字母必须大写,且 tag 拼写准确:
OrderID string `json:"order_id"`,不是json:order_id或json:"order_id"(少引号) - 发送前加校验:
if len(body) == 0 { log.Printf("empty message body for %+v", order) } - 消费者反序列化后,立刻检查关键字段是否为零值:
if order.ID == 0 { return errors.New("missing order ID") } - 推荐统一用指针传参:
json.Marshal(&order),避免值拷贝时嵌套结构体字段被清零
durable 声明,以及消费者 panic 后没触发 Nack 的兜底逻辑。










