
在分布式系统开发中,定时任务是绕不开的需求,但并非所有定时任务都能靠 cron 表达式轻松实现。比如电商订单 20 分钟未支付自动取消、会议开始前半小时自动提醒、工单超期未处理自动告警 ------ 这些非固定起始时间的定时需求,正是延迟队列的拿手好戏。
RabbitMQ 作为主流的消息队列中间件,原生并未直接支持延迟队列,但我们可以通过两种实用方案轻松实现,一个是官方插件(简单易上手,推荐生产使用),一个是原生特性组合(无需额外安装,轻量灵活)。今天就用最通俗的语言,手把手教你玩转 RabbitMQ 延迟队列,看完就能落地!
一、先搞懂:延迟队列到底解决什么问题?
先明确一个核心:固定时间的定时任务用 cron,动态起始的定时任务用延迟队列。
举几个日常开发中高频的延迟队列场景,对号入座看看你有没有遇到过:
- 电商场景:用户下单后,30 分钟内未付款则自动取消订单、释放库存;
- 提醒场景:会议预定成功后,提前半小时给参与人发通知、外卖超时前 10 分钟提醒骑手;
- 工单场景:安全工单 / 售后工单超 24 小时未处理,自动推企业微信 / 钉钉告警;
- 设备场景:智能设备的定时指令,比如设置凌晨 1 点启动设备、下班后自动煮粥。
这些场景的共性是任务的执行时间由事件触发时间决定,无法提前通过 cron 预设,而延迟队列就是专门处理这类需求的利器。
二、方案一:官方延迟插件(推荐)------ 一行配置实现延迟,新手友好
RabbitMQ 官方提供了rabbitmq_delayed_message_exchange延迟消息插件,专门解决延迟队列问题,配置简单、无需维护多套队列、延迟精度高,是生产环境的首选方案。
2.1 第一步:安装并启用插件(Docker 版)
插件安装仅需 4 步,全程命令行操作,复制粘贴就能搞定(以 RabbitMQ 3.9 版本为例):
-
从 GitHub 下载对应版本插件:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases
-
将插件拷贝到 RabbitMQ 容器内:
bashdocker cp ./rabbitmq_delayed_message_exchange-3.9.0.ez 容器名:/plugins -
进入容器并启用插件:
bashdocker exec -it 容器名 /bin/bash rabbitmq-plugins enable rabbitmq_delayed_message_exchange -
验证插件是否启用成功:
bashrabbitmq-plugins list看到
[E*] rabbitmq_delayed_message_exchange就说明启用成功了,执行exit退出容器即可。
2.2 第二步:Spring Boot 代码实现(全程复制可用)
基于 Spring Boot 集成,只需做配置类、消费者、生产者 三步,核心是用CustomExchange自定义交换机,消息头设置延迟时间。
1. 引入依赖
Maven 中引入 RabbitMQ 核心依赖,无需额外依赖:
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 配置 RabbitMQ 连接
在application.properties中配置基础连接信息,本地部署直接用默认值:
XML
spring.rabbitmq.host=localhost
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
3. 核心配置类(定义延迟交换机 + 队列 + 绑定)
关键是交换机类型固定为x-delayed-message,并指定消息分发类型(如 direct):
java
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitDelayConfig {
// 延迟队列名
public static final String DELAY_QUEUE = "delay_queue";
// 延迟交换机名
public static final String DELAY_EXCHANGE = "delay_exchange";
// 交换机类型(固定值)
public static final String EXCHANGE_TYPE = "x-delayed-message";
// 定义延迟队列
@Bean
public Queue delayQueue() {
return new Queue(DELAY_QUEUE, true, false, false);
}
// 定义延迟交换机(CustomExchange)
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct"); // 消息分发类型:direct/fanout/topic
return new CustomExchange(DELAY_EXCHANGE, EXCHANGE_TYPE, true, false, args);
}
// 绑定队列和交换机
@Bean
public Binding delayBinding() {
return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(DELAY_QUEUE).noargs();
}
}
4. 消费者(监听延迟队列,消息到时间自动消费)
简单粗暴,用@RabbitListener监听队列,消息延迟时间到了会自动触发:
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DelayMsgConsumer {
private static final Logger logger = LoggerFactory.getLogger(DelayMsgConsumer.class);
// 监听延迟队列
@RabbitListener(queues = RabbitDelayConfig.DELAY_QUEUE)
public void consumeDelayMsg(String msg) {
logger.info("收到延迟消息:{},消费时间:{}", msg, System.currentTimeMillis());
}
}
5. 生产者(发送消息,设置延迟时间)
核心是在消息头中通过x-delay设置延迟时间(单位:毫秒),3000 即延迟 3 秒:
java
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
@SpringBootTest
public class DelayMsgProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendDelayMsg() {
String msg = "Hello RabbitMQ延迟队列 " + new Date();
logger.info("发送延迟消息:{},发送时间:{}", msg, System.currentTimeMillis());
// 构建消息,设置延迟时间3秒
Message message = MessageBuilder.withBody(msg.getBytes())
.setHeader("x-delay", 3000)
.build();
// 发送消息
rabbitTemplate.convertAndSend(RabbitDelayConfig.DELAY_EXCHANGE, RabbitDelayConfig.DELAY_QUEUE, message);
}
}
启动项目运行测试方法,就能看到控制台3 秒后才消费消息,延迟效果完美实现!
三、方案二:TTL+DLX 原生方案 ------ 无需插件,用 RabbitMQ 自带特性实现
如果你的环境无法安装插件(比如内网隔离),可以用 RabbitMQ原生的 TTL(消息过期时间)+DLX(死信交换机) 组合实现延迟队列,全程基于 RabbitMQ 自带特性,零额外依赖。
3.1 核心思路:死信队列就是你的延迟队列
这个方案的原理特别通俗,记住三句话就行:
- 创建一个普通队列 ,设置消息过期时间(TTL) ,并绑定对应的死信交换机和死信 RoutingKey ,不给这个普通队列配消费者;
- 消息发送到普通队列后,因为没有消费者,会在队列中等待,直到达到过期时间;
- 消息过期后,会被 RabbitMQ 自动转发到死信队列,我们给死信队列配消费者,消息一进入死信队列就被消费,实现延迟效果。
简单说:普通队列负责 "囤消息等过期",死信队列负责 "过期后消费消息"。
3.2 Spring Boot 代码实现(全程复制可用)
同样分三步:配置类、消费者、生产者,核心是给普通队列配置 TTL 和死信相关参数。
1. 配置类(普通队列 + 死信队列双配置)
需要分别定义普通队列 + 普通交换机 、死信队列 + 死信交换机,并给普通队列设置 TTL 和死信绑定:
java
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class TtlDlxConfig {
// 普通队列/交换机/RoutingKey
public static final String NORMAL_QUEUE = "normal_queue";
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static final String NORMAL_ROUTING_KEY = "normal_rk";
// 死信队列/交换机/RoutingKey
public static final String DLX_QUEUE = "dlx_queue";
public static final String DLX_EXCHANGE = "dlx_exchange";
public static final String DLX_ROUTING_KEY = "dlx_rk";
// 1. 配置死信队列
@Bean
public Queue dlxQueue() {
return new Queue(DLX_QUEUE, true, false, false);
}
// 2. 配置死信交换机
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange(DLX_EXCHANGE, true, false);
}
// 3. 绑定死信队列和死信交换机
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(DLX_ROUTING_KEY);
}
// 4. 配置普通队列(核心:设置TTL和死信参数)
@Bean
public Queue normalQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 10000); // 消息过期时间:10秒(单位:毫秒)
args.put("x-dead-letter-exchange", DLX_EXCHANGE); // 绑定死信交换机
args.put("x-dead-letter-routing-key", DLX_ROUTING_KEY); // 绑定死信RoutingKey
return new Queue(NORMAL_QUEUE, true, false, false, args);
}
// 5. 配置普通交换机
@Bean
public DirectExchange normalExchange() {
return new DirectExchange(NORMAL_EXCHANGE, true, false);
}
// 6. 绑定普通队列和普通交换机
@Bean
public Binding normalBinding() {
return BindingBuilder.bind(normalQueue()).to(normalExchange()).with(NORMAL_ROUTING_KEY);
}
}
2. 消费者(仅监听死信队列)
注意:只给死信队列配消费者,普通队列不配,这是实现延迟的关键:
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DlxMsgConsumer {
private static final Logger logger = LoggerFactory.getLogger(DlxMsgConsumer.class);
// 仅监听死信队列
@RabbitListener(queues = TtlDlxConfig.DLX_QUEUE)
public void consumeDlxMsg(String msg) {
logger.info("收到死信队列的延迟消息:{},消费时间:{}", msg, System.currentTimeMillis());
}
}
3. 生产者(发送消息到普通队列)
直接发送消息到普通队列即可,无需额外设置,消息会在普通队列中等待过期:
java
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
@SpringBootTest
public class NormalMsgProducer {
private static final Logger logger = LoggerFactory.getLogger(NormalMsgProducer.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendNormalMsg() {
String msg = "Hello TTL+DLX延迟队列 " + new Date();
logger.info("发送普通消息:{},发送时间:{}", msg, System.currentTimeMillis());
// 发送消息到普通队列
rabbitTemplate.convertAndSend(TtlDlxConfig.NORMAL_EXCHANGE, TtlDlxConfig.NORMAL_ROUTING_KEY, msg);
}
}
启动项目运行测试方法,控制台会显示10 秒后死信队列消费消息,延迟效果实现!
四、两种方案对比:该选哪一个?
看完两种实现方式,你可能会问:生产环境该用哪个?直接看这个对比表,根据自己的场景选择:
表格
| 对比维度 | 官方延迟插件方案 | TTL+DLX 原生方案 |
|---|---|---|
| 安装成本 | 需下载安装插件 | 零安装,原生支持 |
| 配置复杂度 | 低,单队列单交换机 | 高,双队列双交换机,需配置多参数 |
| 延迟精度 | 高,消息到时间立即投递 | 较低,存在队列阻塞问题(前一条消息未过期,后一条也会等待) |
| 灵活性 | 高,每条消息可设置不同延迟时间 | 低,队列级 TTL 固定,所有消息过期时间一致(可设置消息级 TTL 但仍有阻塞问题) |
| 维护成本 | 低,只需维护一套队列 | 高,需维护普通 / 死信两套队列 |
| 生产推荐 | ✅ 强烈推荐,适配 99% 场景 | ❌ 仅推荐无法安装插件的内网 / 特殊环境 |
核心结论 :如果环境允许,优先使用官方延迟插件方案,配置简单、精度高、灵活性强,是 RabbitMQ 官方推荐的延迟队列实现方式;TTL+DLX 仅作为无插件环境的兜底方案。
五、最后总结
RabbitMQ 实现延迟队列的核心就是这两种方案,记住两个核心点:
- 插件方案:用
x-delayed-message类型交换机,消息头x-delay设置延迟时间,简单高效易维护; - TTL+DLX 方案:普通队列囤消息等过期,死信队列负责消费,无插件但配置复杂、精度略低。
其实两种方案的代码都高度复用,本文的代码直接复制到 Spring Boot 项目中,修改队列名和延迟时间就能直接运行,看完赶紧动手试试吧!