基于RabbitMQ实现延迟任务

在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重启时,使用死信队列实现方式会重新统计消息过期时间,而使用延迟交换机实现方式不会
相关推荐
学渣超1 小时前
微服务日志智能诊断系统(九) 让 LLM 成为日志分析专家
java·后端·agent
feelmylife591 小时前
AI系统测试实战指南——从确定性边界到非确定性输出,构建AI系统的分层测试体系
后端
AliCloudROS1 小时前
使用 Skill 安装和管理 ECS 扩展程序
后端
用户298698530141 小时前
Python 实现 Excel 新旧格式互转:告别 Office 依赖的自动化方案
后端·python·excel
JerrySir1 小时前
同一笔请求重来三次,业务只成功一次:技术面试里的幂等性怎么讲
后端
学渣超1 小时前
微服务日志智能诊断系统(七) 日志 RAG 引擎——蒸馏、索引与精准检索
java·后端·agent
学渣超1 小时前
微服务日志智能诊断系统(六) Fast Path vs Slow Path——双路径日志诊断策略
java·后端·agent
ZOnePieceC1 小时前
Go之context
后端