死信队列必须手动声明,rabbitmq不会自动创建dlq;需显式声明持久化队列、交换器并正确绑定,参数须用amqp.table类型,延迟消息推荐使用delayed_message_exchange插件。

死信队列必须手动声明,RabbitMQ 不会自动创建 DLQ
很多人以为只要给业务队列设了 x-dead-letter-exchange,消息过期后就会“自动飞”进某个队列——实际上 RabbitMQ 只负责转发,不建队列、不绑路由、不校验存在性。一旦目标 Exchange 或 Queue 缺失,消息直接丢弃,无日志、无报错、无声无息。
实操建议:
- 先显式声明死信队列,
durable必须为true,且autoDelete为false - 死信 Exchange(如
dlx.order)类型选direct或topic,提前声明并确保持久化 - 用
QueueBind把死信队列绑定到死信 Exchange,routingKey必须和业务队列里设置的x-dead-letter-routing-key完全一致(包括大小写、下划线) - 旧队列参数改了?RabbitMQ 不支持运行时修改,必须删掉重建——否则后续
Publish会直接报Exception (504)
Go 里传死信参数必须用 amqp.Table,不是 map 或 struct
amqp-go 库对队列 arguments 的类型极其敏感:用 map[string]interface{} 构造再转 amqp.Table 才有效;直接传 map 或自定义结构体,参数会被静默忽略——消息照常入队,但永远不会触发死信。
常见错误现象:TTL 到期后消息消失,或 Reject(false) 后没进 DLQ。
实操建议:
- 参数必须用
amqp.Table类型,例如:args := amqp.Table{ "x-dead-letter-exchange": "dlx.order", "x-dead-letter-routing-key": "order.dlq", "x-message-ttl": 30000, "x-max-length": 1000, } -
x-dead-letter-exchange值必须是已存在的 Exchange 名字,字符串,不能是空或未声明的 -
x-message-ttl单位是毫秒,别写成秒(比如 30 秒要写30000,不是30) - 如果每条消息延迟不同,只能设
Expiration字段;若统一延迟,优先设在队列上,避免协议层开销上升
延迟消息必须用插件或 TTL+DLX,原生不支持 x-delay
RabbitMQ 没有内置延迟能力,Expiration 字段只控制消息在队列中存活时间,不等于“准时投递”,更不保证顺序。真要延迟,只有两条路:启用 rabbitmq_delayed_message_exchange 插件,或走 TTL+DLX 组合。
插件法更推荐,TTL+DLX 仅用于无法装插件的旧环境。
实操建议:
- 插件启用命令:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange,要求 RabbitMQ ≥ 3.5.7(当前主流镜像rabbitmq:4-management默认满足) - 声明 Exchange 时必须带
amqp.Table{"x-delayed-type": "direct"},否则ExchangeDeclare直接失败 - 发消息时用
Headers: amqp.Table{"x-delay": 5000},单位毫秒;Expiration字段在此方案下无效 - 该 Exchange 的
autoDeleted必须为false,不支持 auto-delete
消费端必须幂等,重复投递不是异常而是常态
无论插件还是 TTL+DLX,RabbitMQ 都不承诺“最多一次”。Broker 重启、网络抖动、消费者 crash 后重连,都可能导致同一条消息多次抵达死信队列。
真正麻烦的从来不是代码怎么写,而是业务逻辑扛不住重复:比如订单取消动作执行两次、短信发两遍、库存扣双份。
实操建议:
- 用唯一业务 ID(如订单号)+ 状态机判断是否已处理,而不是无条件执行副作用
- 避免在消费逻辑里直接调外部接口或改数据库;应先落库标记“已触发”,再异步执行
- 死信队列本身也要监控:
messages_ready和messages_unacknowledged长期不降,说明消费卡住或路由失败 - 别依赖延迟精度——实测漂移 50–200ms,重点是“最终能触发”,不是“分秒不差”
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











