在RabbitMQ中,实现延迟任务的方式有两种,分别是死信队列和延迟交换机插件
一、使用死信队列实现延迟任务
死信队列:与死信交换机绑定的队列就是死信队列
正常消息变成死信消息主要有三种原因:
- 消息过期。当消息过期时会把消息转发到队列绑定的死信交换机,再由死信交换机发送到对应的死信队列中,由消费者去消费死信队列中的消息
- 消息消费被拒并且消息不重新入队列
- 队列长度过长,消息丢弃
使用死信队列实现延迟任务的原理是,当给消息设置过期时间并且消息过期时,会将消息发送到与队列绑定的死信交换机,死信交换机会把消息转发到死信队列,再由消费者去消费该消息

1. 添加依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2. 在application.yml文件中添加RabbitMQ的连接配置
yaml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
3. 添加队列及交换机的配置
以下实现队列和交换机的拓扑图为: 缓存延迟队列 -> 死信交换机 -> 业务消费队列
java
// 延迟时间固定为5000毫秒
@Configuration
public class DeadLetterTtlConfig {
// 1. 业务消费队列(最终接收到期消息)
public static final String BUSINESS_QUEUE = "business_queue";
// 2. 死信交换机(缓冲队列消息过期后转发到此)
public static final String DLX_EXCHANGE = "dlx_exchange";
// 3. 缓冲延时队列(消息在这里等待TTL超时)
public static final String TTL_QUEUE = "ttl_delay_queue";
// 4. 路由key
public static final String DLX_ROUTE_KEY = "dlx.route";
// 1. 业务队列
@Bean
public Queue businessQueue() {
return new Queue(BUSINESS_QUEUE);
}
// 2. 死信交换机
@Bean
public DirectExchange dlxExchange() {
return ExchangeBuilder.directExchange(DLX_EXCHANGE)
.durable(true)
.build();
}
// 3. 死信交换机与业务队列绑定
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(businessQueue())
.to(dlxExchange())
.with(DLX_ROUTE_KEY);
}
// 4. 延时缓冲队列
@Bean
public Queue ttlDelayQueue() {
// 方式一:
// Map<String, Object> args = new HashMap<>();
// // 消息TTL:5000ms = 5秒延时
// args.put("x-message-ttl", 5000);
// // 指定死信交换机
// args.put("x-dead-letter-exchange", DLX_EXCHANGE);
// // 指定死信路由key
// args.put("x-dead-letter-routing-key", DLX_ROUTE_KEY);
// return QueueBuilder.durable(TTL_QUEUE).withArguments(args).build();
// 方式二:
return QueueBuilder.durable(TTL_QUEUE)
.ttl(5000) // 5秒后过期
.deadLetterExchange(DLX_EXCHANGE) // 绑定死信交换机
.deadLetterRoutingKey(DLX_ROUTE_KEY)
.build();
}
}
4. 消息、生产者及消费者
消息定义如下:
java
@Data
public class DelayMessage {
// 消息id
private String id;
// 消息内容
private String message;
// 消息发送时间
private Long produceTime;
// 消息消费时间
private Long consumeTime;
// 延迟时间
private Long delayTime;
}
生产者配置如下:
java
@Slf4j
@Service
public class TtlMessageSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String message) {
log.info("begin send ttl message: {}", message);
DelayMessage delayMessage = new DelayMessage();
delayMessage.setId(UUID.randomUUID().toString());
delayMessage.setMessage(message);
delayMessage.setProduceTime(System.currentTimeMillis());
delayMessage.setDelayTime(5000L);
rabbitTemplate.convertAndSend(DeadLetterTtlConfig.TTL_QUEUE, JSONObject.toJSONString(delayMessage));
log.info("send ttl message success: {}", message);
}
}
消费者配置如下:
java
@Slf4j
@Service
public class DeadLetterConsumer {
@RabbitListener(queues = DeadLetterTtlConfig.BUSINESS_QUEUE)
public void consume(String message) {
DelayMessage delayMessage = JSONObject.parseObject(message, DelayMessage.class);
delayMessage.setConsumeTime(System.currentTimeMillis());
log.info("死信队列定时任务执行: {}", delayMessage);
log.info("死信队列任务执行时间间隔:{}", delayMessage.getConsumeTime() - delayMessage.getProduceTime());
}
}
生产者发送消息接口:
java
@RestController
@RequestMapping("/deadletter")
public class DeadLetterController {
@Autowired
private TtlMessageSender ttlMessageSender;
@GetMapping("/send/{message}")
public String sendMessage(@PathVariable("message") String message) {
ttlMessageSender.sendMessage(message);
return "success";
}
}
5. 执行结果
调用接口:http://localhost:8080/deadletter/send/hello
接口:
log
2026-07-09 00:14:59.170 INFO 38640 --- [nio-8080-exec-3] c.h.rabbitmq.producer.TtlMessageSender : begin send ttl message: hello
2026-07-09 00:14:59.170 INFO 38640 --- [nio-8080-exec-3] c.h.rabbitmq.producer.TtlMessageSender : send ttl message success: hello
2026-07-09 00:15:04.175 INFO 38640 --- [ntContainer#0-1] c.h.r.consumer.DeadLetterConsumer : 死信队列定时任务执行: DelayMessage(id=a5c9b55b-6f3b-4a36-9969-0e2bb2790850, message=hello, produceTime=1783527299170, consumeTime=1783527304175, delayTime=5000)
2026-07-09 00:15:04.175 INFO 38640 --- [ntContainer#0-1] c.h.r.consumer.DeadLetterConsumer : 死信队列任务执行时间间隔:5005
二、使用延迟交换机实现延迟任务
当并消息发送到延迟交换机时,交换机并不会像普通交换机一样把消息直接转发到队列中,而是先把消息缓存到Mnesia 数据库(RabbitMQ 内置持久化数据库)中,定时扫描数据库里消息,将过期的消息转发到队列中,由消费者进行消费
1. 添加延迟交换机插件
给RabbitMQ添加延迟交换机插件(rabbitmq_delayed_message_exchange-3.13.0.ez),版本需根据RabbitMQ版本选择

2. 添加依赖
参考方法一
3. 添加RabbitMQ连接配置
参考方法一
4. 添加队列及延迟交换机配置
拓扑图为:延迟交换机 -> 队列
java
@Configuration
public class DelayExchangeConfig {
// 延迟交换机名称
public static final String DELAY_EXCHANGE = "delay_exchange";
// 业务队列
public static final String DELAY_QUEUE = "delay_business_queue";
// 路由key
public static final String DELAY_ROUTE_KEY = "delay.route";
// 1. 延迟交换机(类型x-delayed-message)
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
// 底层路由类型 direct
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, args);
}
// 2. 业务队列
@Bean
public Queue delayQueue() {
return QueueBuilder.durable(DELAY_QUEUE).build();
}
// 3. 绑定交换机与队列
@Bean
public Binding delayBinding(Queue delayQueue, CustomExchange delayExchange) {
return BindingBuilder.bind(delayQueue)
.to(delayExchange)
.with(DELAY_ROUTE_KEY)
.noargs();
}
}
5. 添加消息、生产者及消费者配置
消息实体:
java
@Data
public class DelayMessage {
// 消息id
private String id;
// 消息内容
private String message;
// 消息发送时间
private Long produceTime;
// 消息消费时间
private Long consumeTime;
// 延迟时间
private Long delayTime;
}
生产者:
java
@Slf4j
@Service
public class DelayMessageSender {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送延迟消息
*
* @param message 消息内容
* @param delayTime 延迟时间(ms)
*/
public void sendMessage(String message, Long delayTime) {
String msgId = UUID.randomUUID().toString();
log.info("begin send delay message. msgID: {}", msgId);
DelayMessage delayMessage = new DelayMessage();
delayMessage.setId(msgId);
delayMessage.setMessage(message);
delayMessage.setProduceTime(System.currentTimeMillis());
delayMessage.setDelayTime(delayTime);
MessageProperties messageProperties = new MessageProperties();
messageProperties.setHeader("x-delay", delayTime);
Message msg = new Message(JSONObject.toJSONString(delayMessage).getBytes(), messageProperties);
rabbitTemplate.convertAndSend(DelayExchangeConfig.DELAY_EXCHANGE, DelayExchangeConfig.DELAY_ROUTE_KEY, msg);
log.info("send delay message success. msgID: {}", msgId);
}
}
消费者:
java
@Slf4j
@Service
public class DelayMessageConsumer {
@RabbitListener(queues = DelayExchangeConfig.DELAY_QUEUE)
public void delayMessageHandler(String message) {
DelayMessage delayMessage = JSONObject.parseObject(message, DelayMessage.class);
delayMessage.setConsumeTime(System.currentTimeMillis());
log.info("延迟插件定时任务执行: {}", delayMessage);
log.info("延迟插件定时任务执行时间间隔:{}", delayMessage.getConsumeTime() - delayMessage.getProduceTime());
}
}
生产者发送延迟消息接口:
java
@RestController
@RequestMapping("/delay")
public class DelayMessageController {
@Autowired
private DelayMessageSender delayMessageSender;
@RequestMapping("/send/{message}/{delayTime}")
public String sendMessage(@PathVariable("message") String message, @PathVariable("delayTime") Long delayTime) {
delayMessageSender.sendMessage(message, delayTime);
return "success";
}
}
6. 执行结果
调佣接口:http://localhost:8080/delay/send/hello/5000
log
2026-07-09 00:37:00.825 INFO 38640 --- [nio-8080-exec-6] c.h.r.producer.DelayMessageSender : begin send delay message. msgID: 9638d295-bf97-45b9-b6f2-ce39db9a8eac
2026-07-09 00:37:00.826 INFO 38640 --- [nio-8080-exec-6] c.h.r.producer.DelayMessageSender : send delay message success. msgID: 9638d295-bf97-45b9-b6f2-ce39db9a8eac
2026-07-09 00:37:05.831 INFO 38640 --- [ntContainer#1-1] c.h.r.consumer.DelayMessageConsumer : 延迟插件定时任务执行: DelayMessage(id=9638d295-bf97-45b9-b6f2-ce39db9a8eac, message=hello, produceTime=1783528620826, consumeTime=1783528625831, delayTime=5000)
2026-07-09 00:37:05.831 INFO 38640 --- [ntContainer#1-1] c.h.r.consumer.DelayMessageConsumer : 延迟插件定时任务执行时间间隔:5005
三、使用死信队列和延迟交换机实现延迟任务的区别
- 使用死信队列实现时无需给RabbitMQ添加其它依赖,直接使用自带的队列及交换机即可,因此队列和交换机之间的关系会较为复杂;使用延迟交换机实现时需要添加延迟交换机插件,并且需要保证RabbitMQ版本和延迟交换机插件一致,但交换机和队列的关系较为简单,直接将延迟交换机与队列绑定即可
- 使用死信队列实现时必须保证同一队列消息过期时间相同,否则会有队头阻塞问题;使用延迟交换机实现时,消息过期时间可以灵活设置
- RabbitMQ重启时,使用死信队列实现方式会重新统计消息过期时间,而使用延迟交换机实现方式不会