什么是死信队列和延迟消息?
简化版
死信队列(DLQ, Dead Letter Queue) 是专门存放「处理失败、无法被正常消费的消息」的特殊队列——一条消息重试多次仍失败、或过期/被拒绝后,就被投递到死信队列,避免它一直阻塞正常消费,同时保留下来供人工排查或后续处理。延迟消息(Delay Message) 是指消息发送后不立即投递,而是延迟指定时间后才对消费者可见——用于「下单后 30 分钟未支付自动取消」「延迟通知」等定时场景。RocketMQ 原生支持延迟消息(固定延迟级别),RabbitMQ 靠 TTL + 死信或插件实现。
详细版
死信队列——消息进入死信的常见条件:
- 消息被消费者多次重试仍失败(超过最大重试次数)。
- 消息被消费者拒绝(reject/nack)且不重新入队。
- 消息在队列中过期(超过 TTL)未被消费。
- 队列达到最大长度,新消息进不来(RabbitMQ)。
延迟消息——典型应用:
- 订单超时未支付,30 分钟后自动关闭。
- 延迟发送提醒 / 通知。
- 定时任务的替代(到点触发)。
各 MQ 的延迟消息实现:
- RocketMQ:原生支持,预设 18 个延迟级别(1s、5s、10s…1h、2h),发消息时设 delayLevel。
- RabbitMQ:用 TTL(消息过期)+ 死信队列 组合,或用
rabbitmq-delayed-message-exchange插件。 - Kafka:无原生支持,需自己实现(如时间轮 + 定时 Topic 转发)。
完整版教学
一、死信队列:给”处理不了的消息”一个归宿
正常情况下,消息被消费者成功处理后就完成使命。但总有些消息怎么都处理不成功——比如消息格式错误、业务数据异常、依赖的下游一直不可用。如果对这种消息无限重试,会:
- 阻塞正常消费(尤其顺序消费,一条卡住后面全堵);
- 浪费资源(一直重试消耗 CPU、下游压力);
- 刷屏日志、掩盖其他问题。
死信队列就是给这类消息一个专门的「归宿」:当一条消息满足「死信条件」(重试超限、被拒绝、过期等),MQ 就把它从原队列移到死信队列。好处:
- 不再阻塞正常队列,正常消息继续流转。
- 消息不丢,被保留在死信队列里。
- 可人工介入:运维/开发去死信队列查看这些失败消息,分析原因、修复后重新投递或手动处理。
二、消息进入死信的触发条件
不同 MQ 略有差异,但常见触发条件:
- 重试次数耗尽:消费者处理失败会重试,超过最大重试次数(如 RocketMQ 默认 16 次)后,消息进死信队列。
- 消息被拒绝:消费者显式
reject/nack且不要求重新入队(RabbitMQrequeue=false)。 - 消息过期:消息设置了 TTL,在队列里超时未被消费(RabbitMQ)。
- 队列满了:队列达到最大长度限制,新消息无处可放(RabbitMQ)。
三、死信队列的处理策略
消息进了死信队列后怎么办?取决于业务:
- 告警 + 人工处理:监控死信队列,有消息就告警,人工排查原因、决定重投或丢弃。
- 自动重投:修复问题后(如下游恢复),把死信消息重新投回原队列消费。
- 记录归档:把死信消息落库存档,用于对账、审计、问题分析。
死信队列的核心价值是**「隔离 + 保留 + 可追溯」**——把毒消息隔离出来、保留下来、便于事后处理,而不是让它们在正常链路里捣乱或悄悄丢失。
四、延迟消息:让消息”到点再送达”
延迟消息指消息发送后不立即投递给消费者,而是等待指定的延迟时间后才变得可消费。最经典的应用是订单超时关闭:
用户下单时,发一条「延迟 30 分钟」的消息。30 分钟后这条消息才被消费者收到,消费者检查订单状态——如果还是「未支付」就自动关闭订单、释放库存。
用延迟消息实现这类「定时触发」逻辑,比用定时任务轮询数据库(每分钟扫一遍所有未支付订单)高效得多——不用轮询、精准到点、天然分布式。
其他应用:延迟发送通知/提醒、定时开启某活动、重试的退避延迟(第 1 次失败 1 分钟后重试、第 2 次 5 分钟后…)。
五、各 MQ 如何实现延迟消息
- RocketMQ(原生支持):内置固定的延迟级别(默认 18 个:1s、5s、10s、30s、1m、2m…1h、2h)。发消息时设置
delayLevel(如 level=3 表示延迟 10s)。简单好用,但只能选预设级别,不能任意指定时间(RocketMQ 5.x 支持任意时间的定时消息)。 - RabbitMQ(组合实现):经典做法是 TTL + 死信队列——把消息发到一个设置了 TTL、且无消费者的「延迟队列」,消息 TTL 到期后变成死信,被转发到真正的业务队列消费,从而实现延迟。或使用官方
rabbitmq-delayed-message-exchange插件,支持任意延迟时间。 - Kafka(无原生支持):需要自己实现,常见方案是时间轮(TimingWheel)+ 分级 Topic——按延迟时长分成不同 Topic,用定时任务扫描到期消息再转发到目标 Topic。较复杂。
六、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| 死信队列 | 无法正常消费的消息进入专门队列 |
| 延迟消息 | 消息到达后延迟一段时间再投递 |
| 用途 | 失败隔离、人工补偿、超时关闭订单、定时触发 |
consume failed 16 times
or message expired
or queue rejected
-> DLQ
order created -> delay 30min -> check unpaid -> close order
死信用于“失败隔离和补偿”,延迟消息用于“未来某个时间再处理”。
- 误区:死信队列就是丢弃消息。 死信是把异常消息转移到专门队列,便于排查、告警和补偿。
- 误区:延迟消息一定精确到毫秒。 不同 MQ 的延迟精度和实现不同,通常不能当强实时定时器。
- 误区:进了死信就不用管。 死信需要监控、报警、人工或自动补偿,否则只是把问题藏起来。
- 追问:哪些消息会进死信? 消费重试耗尽、消息过期、队列满或被拒绝等都可能进入死信。
- 追问:延迟队列如何实现? 可用 MQ 原生延迟、定时轮询、时间轮、死信 TTL 转发等方式。
- 追问:订单超时关闭为什么适合延迟消息? 创建订单时发延迟消息,到期检查未支付再关闭,避免频繁扫描全表。
七、加强记忆
死信队列(DLQ) = 存放处理失败、无法正常消费的消息的特殊队列,触发条件有重试超限、被拒绝、过期、队列满。价值是隔离毒消息(不阻塞正常队列)+ 保留不丢 + 可人工追溯重投。延迟消息 = 发送后延迟指定时间才可被消费,经典用于订单超时自动关闭(比轮询 DB 高效)、延迟通知、退避重试。实现:RocketMQ 原生支持(预设延迟级别);RabbitMQ 用 TTL + 死信队列组合或延迟插件;Kafka 无原生、需时间轮 + 分级 Topic 自实现。核心区分:死信是「处理不了的消息的归宿」,延迟是「到点才送达的消息」。