死信队列是消息可靠性的兜底机制,必须显式配置DLX参数、手动声明死信交换机,Kafka需应用层双生产者实现;Nack与Reject语义不同,应避免重入队和defer调用;DLQ自身需监控、结构化日志、重试限制及独立配置。
死信不是“额外功能”,而是消息可靠性的兜底开关;不配 DLQ,等于裸奔处理关键业务。
为什么你的消息没进死信队列?
常见现象是:消息处理失败、
也调了,但死信队列里空空如也。根本原因往往在队列声明阶段就漏了关键参数。
用
声明 normal queue 时,必须显式设置
(指向死信交换机)和
(指定路由键),缺一不可
RabbitMQ 不会自动创建死信交换机,你得先手动声明
,且类型要匹配(通常为
或
)
如果同时设置了
和
,TTL 过期后消息才会被投递到 DLX;但若只设了 TTL 没配 DLX,过期消息直接被丢弃,不会进任何队列
Kafka 没有原生死信队列机制,所谓“DLQ”必须靠应用层双生产者实现:主消费者失败后,用另一个
显式发往
主题
Go 中 Nack 和 Reject 怎么选?
两者都会触发死信,但语义和行为不同,选错会导致重试逻辑混乱或消息堆积。
:批量拒绝当前及之前未确认的所有消息,且不重新入队(
)——这是最常用、最安全的失败处理方式
:只拒绝当前消息,不重入队;适合明确不可恢复的错误(如 JSON 解析失败、字段缺失)
或
:让消息重入队头,容易造成无限循环消费;除非你有幂等+指数退避,否则禁用
别在
里调
:goroutine 退出后连接可能已断,调用会 panic 或静默失败
如何避免死信队列自己也“死掉”?
死信队列不是保险箱,它同样需要监控、限流和可追溯性,否则故障会从主链路蔓延到兜底链路。
立即学习
“
go语言免费学习笔记(深入)
”;
DLQ 主题/队列也要配置独立的 consumer group,不能复用主 consumer 的
,否则 offset 提交互相干扰
DLQ 消费者必须记录完整原始消息体 + 错误堆栈 + 时间戳,建议写入结构化日志或专用表(如
),而不是只打
对 DLQ 消息做最大重试次数限制(比如 3 次),超过后转入归档存储(如 S3 / OSS),避免人工介入前持续占用资源
Kafka 场景下,DLQ 生产者要用独立
,尤其注意
设为
,防止 DLQ 消息自己丢失
跨 MQ 迁移时死信逻辑最容易崩在哪?
很多人以为把
换成
就完事,结果线上大量消息重复或丢失。
RabbitMQ 的
是 per-message 的,Kafka 的
是 per-partition+offset range 的,语义完全不同
Kafka 没有“拒绝单条消息”的 API,只能靠业务逻辑跳过处理 + 手动发 DLQ;而 RabbitMQ 可以精准控制哪条进死信
RabbitMQ 死信由 Broker 自动路由,Kafka DLQ 必须由消费者主动发送,这意味着:Kafka 的 DLQ 路径多一次网络 IO、多一个失败点、多一层错误处理
别复用同一套 error 判断逻辑:RabbitMQ 的
和 Kafka 的
类型不兼容,panic 捕获方式也不同
真正难的不是写通 DLQ,而是让每条失败消息都留下可定位的上下文、可控的流转路径和明确的生命周期终点。否则,死信队列只是把问题从眼前挪到了后台日志里。
msg.Nack(true, false)amqp.Table"x-dead-letter-exchange""x-dead-letter-routing-key"dead_exchangedirecttopic"x-message-ttl""x-dead-letter-exchange"kafka.Producer"my-topic-dlq"msg.Nack(true, false)requeue=falsemsg.Reject(false)msg.Nack(false, true)msg.Reject(true)deferNackgroup.iddlq_eventslog.Printf("dlq: %v", err)ConfigMap"acks""all"msg.Ack()consumer.CommitOffsets()Ack/NackCommitOffsetsamqp.Errorkafka.Error