这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
RabbitMQ
RabbitMQ 本身不支持延时消息,但可以通过插件实现。
安装插件
RabbitMQ 的延时消息能力由插件提供。启用插件的命令是:
# 列出当前已启用的插件
rabbitmq-plugins list
# 启用延时消息交换器插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 启用后确认状态为 [E*](E 表示已启用,* 表示进程正在运行)
rabbitmq-plugins list | grep delayed
验证要点
- 命令只用于验证系统状态,读者不需要记具体参数。
- 启用插件需要重启 RabbitMQ 节点;集群里每个节点都要启用,否则消息路由到未启用插件的节点会失败。
- 这个插件现在不是官方文档里的推荐方案。 官方文档站的 “Delayed Message Exchange” 页面在 RabbitMQ 3.13 与 4.x 两棵文档树里都已不存在,说明官方已不再把它当作一等公民来维护。工程上更稳妥的是下面第二节的 TTL + 死信交换器方案,它只用 RabbitMQ 的核心特性,不需要插件。
基本使用
启用插件后,多了一条 x-delayed-message 类型的交换器。声明时把延时时长作为参数传进去:
rabbitmqadmin declare exchange \
name=order.delayed \
type=x-delayed-message \
durable=true \
arguments='{"x-delayed-type": "direct"}'
声明之后,发布消息时用 x-delay 头指定要等多少毫秒:
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
channel = connection.channel()
# 声明延时交换器(参数要和上面一致,否则报 inequivalent arg)
channel.exchange_declare(
exchange="order.delayed",
exchange_type="x-delayed-message",
durable=True,
arguments={"x-delayed-type": "direct"},
)
# 发布一条 30 分钟(1800000 毫秒)后才投递的消息
channel.basic_publish(
exchange="order.delayed",
routing_key="order.cancel",
body=b'{"order_id": "ORD20241015001"}',
properties=pika.BasicProperties(
headers={"x-delay": 30 * 60 * 1000},
delivery_mode=2, # 持久化消息
),
)
connection.close()
几个必须注意的点:
x-delay的单位是毫秒,不是秒。写错单位是这个插件最常见的低级错误——想等 30 分钟却写成30,消息会在 30 毫秒后就被投递。- 消息必须持久化(
delivery_mode=2)且交换器必须是durable=true,否则节点重启时延时中的消息会丢——因为延时消息在插件内部是暂存在内存里的。 - 延时时长有上限。插件依赖 Erlang 的定时器,延时过长的消息(通常在数小时到数天量级,具体取决于版本)可能失败或被拒绝。跨越很长时间的任务(比如”7 天后自动确认收货”)不适合用它。
- 插件的延时精度不如轮询。它是”到点后尽力投递”,受节点负载影响;如果业务对到点精度有硬要求,需要配合确认机制与补偿扫描。
如果不想依赖插件,RabbitMQ 的核心特性也能拼出延时队列。原理是给消息设置过期时间(TTL),过期后由死信交换器(Dead Letter Exchange, DLX)转发到真正要消费的队列。
# 真正的业务队列
rabbitmqadmin declare queue name=order.cancel durable=true
rabbitmqadmin declare queue name=order.cancel.dlx durable=true \
arguments='{"x-dead-letter-exchange": "", "x-dead-letter-routing-key": "order.cancel"}'
# 延时队列:消息在这里等过期,过期后被转发到 order.cancel
rabbitmqadmin declare queue name=order.delay durable=true \
arguments='{"x-message-ttl": 1800000, "x-dead-letter-exchange": "", "x-dead-letter-routing-key": "order.cancel"}'
发布时把消息投递到 order.delay,它会在 30 分钟后自动出现在 order.cancel:
channel.basic_publish(
exchange="",
routing_key="order.delay",
body=b'{"order_id": "ORD20241015001"}',
properties=pika.BasicProperties(delivery_mode=2),
)
它的优势是只用核心特性,不装插件,且消息在队列里是持久化的(比插件的内存暂存可靠)。
代价是队列头部的过期消息不会被及时清理——RabbitMQ 只在消息到达队头时才检查是否过期,所以大量过期消息堆积在队列中间时会延迟投递。标准解法是按延时档位建多个队列(比如 order.delay.30m、order.delay.1h),每个队列用自己的 TTL,到期后统一转发到同一个业务队列。
优点
- ✅ 原生分布式支持
- ✅ 高可靠性(持久化、ACK)
- ✅ 易于集成
- ✅ 延时时长可以在每条消息上单独指定,不需要为不同延时档位建不同的队列
缺点
- ❌ 需要安装额外插件
- ❌ 延时时间有限(受限于插件实现)
- ❌ 运维复杂度高
- ❌ 官方文档已不再收录该插件,长期维护性存疑
- ❌ 延时消息在插件内部暂存内存,节点重启或内存压力下有丢失风险
发现内容有问题?
告诉我们哪里需要改进。
