RabbitMQ 延迟队列实现指南:两种方案手把手教你搞定

在分布式系统开发中,定时任务是绕不开的需求,但并非所有定时任务都能靠 cron 表达式轻松实现。比如电商订单 20 分钟未支付自动取消、会议开始前半小时自动提醒、工单超期未处理自动告警 ------ 这些非固定起始时间的定时需求,正是延迟队列的拿手好戏。

RabbitMQ 作为主流的消息队列中间件,原生并未直接支持延迟队列,但我们可以通过两种实用方案轻松实现,一个是官方插件(简单易上手,推荐生产使用),一个是原生特性组合(无需额外安装,轻量灵活)。今天就用最通俗的语言,手把手教你玩转 RabbitMQ 延迟队列,看完就能落地!

一、先搞懂:延迟队列到底解决什么问题?

先明确一个核心:固定时间的定时任务用 cron,动态起始的定时任务用延迟队列

举几个日常开发中高频的延迟队列场景,对号入座看看你有没有遇到过:

  1. 电商场景:用户下单后,30 分钟内未付款则自动取消订单、释放库存;
  2. 提醒场景:会议预定成功后,提前半小时给参与人发通知、外卖超时前 10 分钟提醒骑手;
  3. 工单场景:安全工单 / 售后工单超 24 小时未处理,自动推企业微信 / 钉钉告警;
  4. 设备场景:智能设备的定时指令,比如设置凌晨 1 点启动设备、下班后自动煮粥。

这些场景的共性是任务的执行时间由事件触发时间决定,无法提前通过 cron 预设,而延迟队列就是专门处理这类需求的利器。

二、方案一:官方延迟插件(推荐)------ 一行配置实现延迟,新手友好

RabbitMQ 官方提供了rabbitmq_delayed_message_exchange延迟消息插件,专门解决延迟队列问题,配置简单、无需维护多套队列、延迟精度高,是生产环境的首选方案。

2.1 第一步:安装并启用插件(Docker 版)

插件安装仅需 4 步,全程命令行操作,复制粘贴就能搞定(以 RabbitMQ 3.9 版本为例):

  1. 从 GitHub 下载对应版本插件:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases

  2. 将插件拷贝到 RabbitMQ 容器内:

    bash 复制代码
    docker cp ./rabbitmq_delayed_message_exchange-3.9.0.ez 容器名:/plugins
  3. 进入容器并启用插件:

    bash 复制代码
    docker exec -it 容器名 /bin/bash
    rabbitmq-plugins enable rabbitmq_delayed_message_exchange
  4. 验证插件是否启用成功:

    bash 复制代码
    rabbitmq-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 核心思路:死信队列就是你的延迟队列

这个方案的原理特别通俗,记住三句话就行:

  1. 创建一个普通队列 ,设置消息过期时间(TTL) ,并绑定对应的死信交换机和死信 RoutingKey不给这个普通队列配消费者
  2. 消息发送到普通队列后,因为没有消费者,会在队列中等待,直到达到过期时间;
  3. 消息过期后,会被 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 实现延迟队列的核心就是这两种方案,记住两个核心点:

  1. 插件方案:用x-delayed-message类型交换机,消息头x-delay设置延迟时间,简单高效易维护;
  2. TTL+DLX 方案:普通队列囤消息等过期,死信队列负责消费,无插件但配置复杂、精度略低。

其实两种方案的代码都高度复用,本文的代码直接复制到 Spring Boot 项目中,修改队列名和延迟时间就能直接运行,看完赶紧动手试试吧!

相关推荐
程序员泠零澪回家种桔子2 小时前
Sentinel核心能力解析:限流与集群方案
后端·架构·sentinel
信码由缰2 小时前
Spring Boot 面试问题
spring boot·后端·面试
一路向北⁢2 小时前
Spring Boot 3 整合 SSE (Server-Sent Events) 企业级最佳实践(三)
java·spring boot·后端·sse
qq_297574672 小时前
SpringBoot项目长时间未访问,Tomcat临时文件夹被删除?解决方案来了
spring boot·后端·tomcat
一个有梦有戏的人2 小时前
Python3基础:函数基础,解锁模块化编程新技能
后端·python
逍遥德3 小时前
Sring事务详解之02.如何使用编程式事务?
java·服务器·数据库·后端·sql·spring
qq_2975746711 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端
为什么不问问神奇的海螺呢丶17 小时前
n9e categraf rabbitmq监控配置
分布式·rabbitmq·ruby