必须单例复用amqp.Connection并配置心跳,每次发消息都调用amqp.Dial()会导致端口耗尽、RabbitMQ断连或OOM;Channel需按需创建并及时关闭,队列声明参数须一致,消费者须设置QoS避免消息堆积。

Go 程序里反复调用 amqp.Dial() 是最危险的操作——连接数暴涨、端口耗尽、RabbitMQ 主动断连,90% 的线上抖动都源于此。
为什么不能每次发消息都 amqp.Dial()
AMQP 连接是重量级 TCP 长连接,不是 HTTP 那种即开即关的轻量连接。每次 amqp.Dial() 都会:
- 占用一个新端口(Linux 默认 65535 个 ephemeral port,很快耗尽)
- 触发 RabbitMQ 的 connection limit 检查(默认 65536,但实际受内存和 fd 限制)
- 心跳未配时,K8s Service 或云 LB 在 30 秒空闲后静默 kill 连接,导致后续 ch.Publish() panic
- 多实例部署时,每个 Pod 都疯狂建连,RabbitMQ 直接 OOM
amqp.Connection 必须单例 + 心跳配置
全局只初始化一次 *amqp.Connection,封装在包级变量或 DI 容器中:
var conn *amqp.Connection
<p>func initRabbitMQ() {
cfg := amqp.Config{
Heartbeat: 10 * time.Second,
// 注意:Dial 默认 heartbeat=10s,但显式写出来更安全
// 若 RabbitMQ server 配置了 heartbeat=30s,client 必须 ≤ server 值
}
var err error
conn, err = amqp.Dial("amqp://guest:guest@localhost:5672/", cfg)
if err != nil {
log.Fatal(err)
}
}
-
Heartbeat必须设,否则云环境/容器网络下连接大概率被中间设备掐断 - 不要用
amqp.DialConfig()以外的方式传 config;amqp.Dial()的 config 参数是可选的,但省略就等于放弃心跳控制 - 连接失败要重试(加 backoff),不能直接 panic —— 启动时 RabbitMQ 可能还没 ready
amqp.Channel 不能复用,必须按需创建 + 立即关闭
*amqp.Channel 不是线程安全的,跨 goroutine 共享会 panic。正确姿势是:
- 每次 publish / consume 前调用
conn.Channel()新建一个 - 用完立刻
ch.Close()(defer 最稳妥) - 别把
ch存成全局变量或塞进 struct 里长期持有 - 消费者 callback 里收到的
msg,其Ack()必须在同一个ch上调用 —— 别只传msg不传ch
队列声明参数不匹配会导致消息丢失
生产者和消费者对同一队列的 QueueDeclare() 参数必须一致,尤其:
立即学习“go语言免费学习笔记(深入)”;
-
durable:设为true才能保证 RabbitMQ 重启后队列不丢(默认false) -
autoDelete:设为false,否则最后一个 consumer 断开后队列被删,后续消息无处投递 -
exclusive:多实例部署时必须false,否则第二个服务起不来(排他队列只允许一个 connection 绑定) - 交换机类型和 routing key 要对得上:
ch.Publish("", queueName, ...)走默认 exchange,只认queueName当 routing key;若用自定义 exchange,第一个参数必须填 exchange 名,且提前ExchangeDeclare()
最容易被忽略的是:消费者没设 qos(ch.Qos(1, 0, false))又禁用 autoack,结果一个 channel 拿到一堆消息却卡死,其他 consumer 彻底饿死。


















