跨服务异步解耦必须走持久化消息队列,因HTTP handler内直接publish或HTTP调用仅掩盖同步错误;需用缓冲channel解耦、全局复用带超时心跳的AMQP连接、显式设Persistent+mandatory、启用publisher confirm、consumer首行defer msg.Ack(false)并设QoS。

跨服务异步解耦必须走持久化消息队列,Gin/Echo handler 里直接 go publish() 或 http.Post() 不算解耦,只是把同步错误藏得更深。
为什么不能在 HTTP handler 里直接调 ch.Publish()
现象是接口返回快,但下游一挂,消息就静默丢失,连日志都难查——因为 w.WriteHeader() 或 w.Write() 后,底层连接可能被复用或关闭,此时再调 ch.Publish() 要么 panic,要么丢消息且无提示。
- handler 只做两件事:
json.Marshal()消息体、写入带缓冲的chan []byte(如make(chan []byte, 1000)) - 独立 goroutine 持续消费该 channel,封装在
publishToRabbitMQ()中统一处理重试、连接重建、失败落库 - 绝对禁止在中间件或 handler 内调
amqp.Connection.Channel()或ch.Publish()
amqp.Dial() 必须带超时和心跳,且全局复用 *amqp.Connection
每发一条消息就 amqp.Dial(),高并发下很快报 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 重启或断电全丢——这不是异步,是“假装发了”。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
-
durable: true声明队列,否则DeliveryMode: amqp.Persistent无效 -
publishing.DeliveryMode必须显式设为amqp.Persistent,且mandatory: true让路由失败时立即返回 error - 别信“发了就完了”,真可靠要配 publisher confirm +
ch.NotifyPublish()监听确认
消费者不先 defer msg.Ack(false) 就是埋雷
RabbitMQ 的 consumer 是“收到→干活→显式 Ack→再收下一条”,不是“收到就干”。很多人把 msg.Ack(false) 写在业务逻辑中间,一旦前面 panic、return 或 DB 报错,Ack 就被跳过——消息被 hold 住,最终积压、触发流控、拖垮 channel。
- consumer handler 开头第一行就写
defer msg.Ack(false),确保无论怎么退出都执行 - 业务逻辑成功后再调
msg.Ack(true),否则消息会重回队列 - 必须设
ch.Qos(1, 0, false)控制预取数,否则 RabbitMQ 会一口气推几百条,OOM 或重启时全丢 - 幂等校验不能省:比如用
redis.SetNX("processed:task_123", "1", time.Hour)
真正可靠的异步解耦,从来不是靠“发出去就放心”,而是每一层都有明确责任边界:连接谁管、channel 谁关、消息谁确认、失败谁兜底。最容易被忽略的是 defer msg.Ack(false) 的位置和 ch.Qos 的设置——它们不出问题时毫无存在感,一出就是大面积积压。

















