RabbitMQ 延迟队列
延迟队列让消息"过一段时间才被投递",常用于订单超时关单、定时提醒等。RabbitMQ 本身没有内置延迟队列,主流做法有三种:TTL+死信组合、官方延迟插件、应用侧定时任务。
三种方案对比
| 方案 | 实现成本 | 延迟精度 | 适用 |
|---|---|---|---|
| TTL + 死信 | 低(零插件) | 毫秒级,受队头阻塞影响 | 固定几种延迟时长 |
| 官方延迟插件 | 中(需启用插件) | 秒级调度,一般够用 | 每条消息延迟可不同 |
| 应用定时任务 | 高(要建任务表) | 由轮询周期决定 | 海量延迟任务的兜底 |
方案一:TTL + 死信(经典方案)
核心思路:消息先进一个无人消费、带 x-message-ttl 的延迟队列;TTL 到期后消息被当作死信转投到目标交换机,从而"延迟后"到达真正消费它的队列:
# 1. 声明业务交换机、业务队列并绑定
rabbitmqadmin declare exchange name=biz.ex type=direct durable=true
rabbitmqadmin declare queue name=q.order durable=true
rabbitmqadmin declare binding source=biz.ex destination=q.order routing_key=order.close
# 2. 声明延迟队列:10 秒 TTL,到期转死信到 biz.ex
rabbitmqadmin declare queue name=q.delay.10s \
arguments='{"x-message-ttl":10000,"x-dead-letter-exchange":"biz.ex","x-dead-letter-routing-key":"order.close"}'
生产者把消息发进延迟队列(直接按队列名投递即可):
import pika
ch = pika.BlockingConnection(pika.ConnectionParameters("127.0.0.1")).channel()
ch.basic_publish(exchange="", routing_key="q.delay.10s",
body=b"close order 10086")
10 秒后消息过期 → 转死信到 biz.ex(routing key=order.close)→ 落入 q.order 被正常消费。
注意两点:x-message-ttl 对整队列统一,需要几种延迟时长就建几个延迟队列(如 10s、30s、5m 各一个);消息经历过一次"过期再入队",转发顺序不保证,延迟期间也无法从队列中撤单,消费端要按业务状态(是否已支付)自行判断是否执行。
方案二:官方延迟插件
官方提供 rabbitmq_delayed_message_exchange 插件,启用后声明一种 x-delayed-message 类型交换机,消息带 x-delay 头即按毫秒延迟投递:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 输出:Enabling plugins on node rabbit@myhost ... done
# 提示:启用该插件需要重启 broker 生效
# 声明延迟交换机:x-delayed-type 指定底层路由类型(direct/topic/fanout)
rabbitmqadmin declare exchange name=delay.ex type=x-delayed-message \
arguments='{"x-delayed-type":"direct"}'
import pika
ch = pika.BlockingConnection(pika.ConnectionParameters("127.0.0.1")).channel()
props = pika.BasicProperties(headers={"x-delay": 30000}) # 延迟 30 秒(毫秒)
ch.basic_publish(exchange="delay.ex", routing_key="order.close",
body=b"close order 10086", properties=props)
插件由官方维护发布,延迟期内消息由插件内部保管、到期再按 x-delayed-type 的路由规则转发,每条消息可单独指定延迟时长,适合"延迟各不同"的场景;插件版本需与 broker 配套,升级前先查兼容性。
精度与选择建议
TTL+死信的精度由 broker 队头检查决定,理论上可到毫秒级,但队头阻塞会让实际延迟偏大;插件按内部调度到期转发,一般按秒级精度评估。延迟消息量大且时长很长的任务,两种 MQ 方案都会长期占资源,可考虑应用侧定时任务表轮询兜底。
典型场景
- 订单超时自动关单:下单后 30 分钟发延迟消息,消费时查支付状态,未支付才关单;
- 定时提醒:注册 24 小时后发送召回提醒;
- 重试退避:失败消息按递增延迟重新入队重试。
小结
延迟队列本质是把"到期再投"转译成过期死信或插件延迟:固定时长选 TTL+死信,逐条可变延迟选官方 x-delayed-message 插件;所有方案都要在消费端用业务状态兜底,避免延迟消息到点后误执行。