最常见原因是exchange或routing_key配置为空导致消息被静默丢弃;需显式声明exchange、绑定队列,启用mandatory模式捕获未路由消息,关闭autoack并手动ack/nack,复用连接但不复用channel,队列与消息设持久化,json字段须导出且tag正确。

为什么 RabbitMQ 的消息发出去却没人收到?
最常见原因是 exchange 或 routing_key 配置为空,导致消息被 RabbitMQ 静默丢弃。默认交换机(AMQP default exchange)只支持 direct 路由,且要求 routing_key 必须和队列名完全一致;一旦用错,消费者永远收不到,日志里也看不出异常。
- 生产者必须显式声明一个 exchange(如
"orders.exchange"),再通过ch.ExchangeDeclare()创建,并用ch.QueueBind()绑定队列,别依赖空字符串 - 检查
ch.Publish()第一个参数是否为有效 exchange 名,第二个参数是否为已绑定的routing_key - 用
amqp.Publishing{Mandatory: true}并监听conn.NotifyPublish(),可捕获“消息未被路由”的情况
消费者 panic 后订单扣了两次库存?
这是 autoAck 开关没关导致的典型数据错乱:设 autoAck: true 时,RabbitMQ 一投递就标记消息为已确认,哪怕你的业务逻辑 panic 或数据库写失败,消息也不会重试。
- 务必设
autoAck: false,并在业务逻辑完整执行(如 DB 更新成功、缓存刷新完成)后,才调用msg.Ack(false) - 遇到不可恢复错误(如 JSON 解析失败、关键字段缺失),用
msg.Nack(false, true)让消息重回队尾重试 - 所有
msg.Ack()/msg.Nack()必须在同一个amqp.Channel上调用,跨 goroutine 传递amqp.Delivery时别漏传 channel 实例
如何避免连接风暴和 Channel 泄露?
微服务长期运行,不能每次发消息都 amqp.Dial() + conn.Close(),否则很快端口耗尽;也不能把 amqp.Channel 当全局变量复用,它不是线程安全的。
- 用单例或依赖注入管理
*amqp.Connection,连接时配置amqp.Config{Heartbeat: 10 * time.Second}防 K8s 网络策略断连 - 每个 goroutine 自行调用
conn.Channel()获取新 channel,处理完立刻ch.Close() - 监听
conn.NotifyClose(),收到信号后触发重连逻辑,而不是等下一次 Publish 才发现断连 - 队列声明必须设
durable: true,消息发布必须设DeliveryMode: amqp.Persistent,否则 RabbitMQ 重启后消息全丢
JSON 序列化后是空对象 {} 怎么办?
Go 的 json.Marshal() 对非导出字段(小写开头)静默忽略,也不报错;你看到空 body,大概率是结构体字段没导出或 json: tag 拼错了。
立即学习“go语言免费学习笔记(深入)”;
- 确保字段首字母大写(如
ID int `json:"id"`),且 tag 冒号后有双引号:json:"order_id",不是json:order_id或json:"order_id - 发送前加校验:
if len(body) == 0 { log.Fatal("empty JSON body") } - 消费端反序列化后,立即检查关键字段(如
v.ID)是否为零值,早暴露映射失败 - 用指针传参:
json.Marshal(&order),避免值拷贝时零值字段干扰
RabbitMQ 在 Go 微服务里不是“配个地址就能用”的玩具,连接复用、channel 生命周期、ACK 时机、JSON 映射细节——每一步漏掉都可能在线上引发重复扣款、消息丢失或连接雪崩。真正稳住,靠的是对每个 amqp. 函数行为的确定性理解,而不是堆配置。










