真解耦必须走持久化消息队列;HTTP handler只序列化消息并写入带缓冲channel,由独立goroutine调publishToRabbitMQ统一重试、重建连接、失败落库。

goroutine 直接发 HTTP 或调函数不是异步任务解耦,只是把同步操作藏进后台——下游一挂,协程堆积,消息静默丢失。真解耦必须走持久化消息队列。
为什么不能在 HTTP handler 里直接 ch.Publish()
HTTP 响应一旦写出(w.WriteHeader 或 w.Write),底层连接可能被复用或关闭。此时再调 ch.Publish(),轻则 panic,重则消息无声丢弃,且无日志可查。
- handler 只做两件事:序列化消息体、写入带缓冲的
chan []byte(如make(chan []byte, 1000)) - 用独立
goroutine持续消费该 channel,并在封装函数publishToRabbitMQ()中统一处理重试、连接重建、失败落库 - 绝对禁止在 Gin/Echo 中间件或 handler 内直接调
amqp.Connection.Channel()或ch.Publish()
amqp.Dial 连接必须带超时和心跳
每次 amqp.Dial 都新建 TCP 连接,高并发下很快耗尽本地端口,报 connect: cannot assign requested address;DNS 解析失败时还默认无限阻塞。
- 用
sync.Once初始化全局*amqp.Connection - URL 中必须含
connect_timeout=5和heartbeat=30 - 监听
conn.NotifyClose,触发后清空旧连接,用指数退避(1s → 2s → 4s)重建 -
Channel按需创建、用完即ch.Close();泄漏会导致 RabbitMQ 报channel error: too many channels
发消息不设 DeliveryMode: amqp.Persistent 就等于没发
DeliveryMode: amqp.Transient 意味着消息只存在内存,Broker 重启或断电全丢——这不是异步,是“假装发了”。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 声明队列时必须传
durable: true,否则DeliveryMode: amqp.Persistent无效 -
amqp.Publishing中必须设DeliveryMode: amqp.Persistent和mandatory: true -
mandatory: true让路由失败(如 exchange 不存在、binding 缺失)时立即返回 error,而不是静默丢弃
消费者不手动 defer msg.Ack(false) 就是埋雷
RabbitMQ 的 consumer 不是“收到就干”,而是“收到→干活→显式 Ack→再收下一条”。很多人把 msg.Ack(false) 写在业务逻辑中间,一旦前面 panic、return 或数据库报错,Ack 就被跳过——消息被 RabbitMQ 一直 hold 住,最终积压、触发流控、拖垮整个 channel。
立即学习“go语言免费学习笔记(深入)”;
- consumer handler 开头第一行就写
defer msg.Ack(false),确保无论怎么退出都执行 - 业务逻辑成功后再调
msg.Ack(true),否则消息会重回队列 - 务必做幂等:比如用
task_id写SETNX,防止重复消费

















