最干净的延迟队列方案是使用rabbitmq_delayed_message_exchange插件,但需确保RabbitMQ服务端已启用该插件(执行rabbitmq-plugins list | grep delayed确认[E*]标记),版本不低于3.5.8,且Spring Boot中必须用CustomExchange声明x-delayed-message类型交换机、通过MessagePostProcessor设置x-delay消息头(单位毫秒),而非TTL或setDelay方法。

直接用 rabbitmq_delayed_message_exchange 插件是最干净的延迟队列方案,但前提是 RabbitMQ 服务端已启用该插件——Spring Boot 本身不提供插件能力,它只负责声明交换机、发消息、设头。
确认 RabbitMQ 服务端已启用 x-delayed-message 插件
这是最容易卡住的第一步。Spring Boot 配置再正确,服务端没装插件,CustomExchange 声明会失败,或者发消息时抛出 channel error: channel exception。
- 登录 RabbitMQ 服务器,执行
rabbitmq-plugins list | grep delayed,看到rabbitmq_delayed_message_exchange后面带[E*]才算启用成功 - 若未启用:把对应版本的
.ez插件文件(如rabbitmq_delayed_message_exchange-3.12.0.ez)拷进plugins/目录,再运行rabbitmq-plugins enable rabbitmq_delayed_message_exchange - 必须重启 RabbitMQ 服务(不是 reload),否则插件不加载;3.11+ 版本支持热启用,但生产环境仍建议重启以确保 mnesia 表初始化完成
- RabbitMQ 版本不能低于 3.5.8,2026 年主流部署已是 3.11.x 或 3.12.x,旧版请先升级
Spring Boot 中声明 x-delayed-message 类型的 CustomExchange
不能用 DirectExchange 或 TopicExchange,必须用 CustomExchange,且类型名严格为 "x-delayed-message",拼错一个字母都会报 NOT_FOUND - no exchange 'xxx' in vhost '/'。
-
CustomExchange构造时第 2 个参数是 type,必须写死为"x-delayed-message" - 必须通过
args.put("x-delayed-type", "direct")指定内部路由逻辑(支持direct/topic/fanout),否则创建交换机失败 - 该交换机默认不自动持久化,需显式传
true(第 3 个参数);否则服务重启后交换机消失,但队列还在,造成路由中断 - 不要在
@Bean方法里做任何耗时操作或依赖其他未初始化 Bean,否则启动报BeanCurrentlyInCreationException
@Bean
public Exchange delayExchange() {
Map<string object> args = new HashMap();
args.put("x-delayed-type", "direct");
return new CustomExchange("delay.exchange", "x-delayed-message", true, false, args);
}</string>
发送消息时设置 x-delay 头而非 TTL
插件模式下,延迟靠的是消息 Header 中的 x-delay,不是队列 TTL 或消息 TTL。设错位置会导致消息立刻入队或被丢弃。
- 必须用
MessagePostProcessor在convertAndSend时设置:message.getMessageProperties().setHeader("x-delay", 60_000) - 不能用
message.getMessageProperties().setDelay(60_000)—— 这是 Spring AMQP 对 DLX 方案的封装,对 x-delayed-exchange 无效 -
x-delay单位是毫秒,最大值受 RabbitMQ 配置限制(默认约 7 天,即max_time_since_publish),超限会静默丢弃 - 如果用
template.setMandatory(true),消息因路由失败或延迟超限被拒绝时,会触发ReturnCallback,可用于告警
消费者无需特殊配置,但要注意顺序和堆积
插件把消息暂存在 mnesia 表里,到点才推给队列,所以消费者看到的仍是普通队列消费流程,但背后行为完全不同。
- 消息投递顺序 = 到达交换机时间 + x-delay 值,不是入队时间;同一交换机内,延迟短的消息一定比延迟长的先到队列
- 高并发写入大量不同延迟的消息时,mnesia 表可能成为瓶颈,监控
delayed_messages指标,持续 > 10k 需扩容或分桶(如按分钟建多个 exchange) - 插件不保证严格准时(误差通常在 100ms 内),也不保证跨节点强一致;若需亚秒级精度或跨 AZ 可靠性,应换用 Redis ZSET + 定时扫描
- 消息体大小建议 ≤ 64KB,过大时 mnesia 写入变慢,且无法分片,容易触发内存告警
真正麻烦的从来不是代码怎么写,而是插件是否生效、x-delay 是否被识别、以及延迟时间设大了之后 mnesia 表悄悄吃满内存却没人发现——这些点不提前盯住,上线后问题只会出现在凌晨三点的报警电话里。










