从零起步学习RabbitMQ || 第四章:RabbitMQ的延迟消息在项目中的运用及实现剖析

在实际项目和面试中,RabbitMQ 延迟消息几乎是一个必问点,尤其常常和下面这个场景绑定出现:

订单超时未支付,如何自动取消?

很多同学:

  • 知道用 MQ

  • 知道死信队列

  • 但一问原理就说不清

本文将以 "面试官视角" + "真实项目设计" 的方式,系统讲清:

  • RabbitMQ 为什么不直接支持延迟队列

  • 延迟消息的 两种主流实现方案

  • 订单超时取消的 完整设计 + Java 代码

读完这篇文章,你至少能在面试中:

✅ 把延迟消息讲清楚

✅ 把订单超时方案说明白

✅ 写出能落地的代码


一、面试引入:订单超时关闭你是怎么做的?

这是一个非常经典的后端面试题,本质考察三点:

  1. 你有没有真实做过业务

  2. 你对 MQ 的理解是不是停留在 API 层

  3. 你是否具备系统设计能力

业务规则(标准描述)

  • 用户下单,生成订单(状态:待支付)

  • 给用户 30 分钟支付时间

  • 超过 30 分钟仍未支付,订单自动取消

❌ 错误 / 低分答案

  • 用定时任务每分钟扫数据库

  • 用 while + sleep 轮询

✅ 高分答案方向

下单时发送一条延迟消息,30 分钟后自动检查订单状态,未支付则取消。

这就是 RabbitMQ 延迟消息的典型应用。


二、RabbitMQ 延迟消息的两种实现方案(面试重点)

面试常问

RabbitMQ 支持延迟队列吗?

标准回答:

RabbitMQ 本身不直接支持延迟队列 ,但可以通过 死信队列延迟消息插件 实现。

方案 是否官方 面试推荐度 特点
死信交换机(TTL + DLX) ⭐⭐⭐⭐⭐ 原理题必问
延迟消息插件 ❌(插件) ⭐⭐⭐⭐ 实战更优

三、方案一:死信队列实现延迟消息(必须会)

1️⃣ 面试官最想听到的原理

一句话总结:

给消息设置 TTL,过期后进入死信交换机,再由消费者处理。

完整流程:

  1. 下单后发送消息到「延迟队列」

  2. 消息设置 TTL = 30 分钟

  3. 消息过期,成为死信

  4. 死信被路由到死信队列

  5. 消费者监听死信队列,检查并取消订单

如果你能把这 5 步说清楚,原理题直接拿分


2️⃣ MQ 结构设计(画图更加分)

组件 名称 作用
Exchange order.delay.exchange 延迟交换机
Queue order.delay.queue 延迟队列
Exchange order.dlx.exchange 死信交换机
Queue order.dlx.queue 真正消费

3️⃣ Spring Boot 配置(项目级答案)

java 复制代码
@Configuration
public class RabbitMQConfig {

    @Bean
    public DirectExchange delayExchange() {
        return new DirectExchange("order.delay.exchange");
    }

    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange("order.dlx.exchange");
    }

    @Bean
    public Queue delayQueue() {
        return QueueBuilder.durable("order.delay.queue")
                .withArgument("x-dead-letter-exchange", "order.dlx.exchange")
                .withArgument("x-dead-letter-routing-key", "order.dlx.key")
                .build();
    }

    @Bean
    public Queue dlxQueue() {
        return new Queue("order.dlx.queue", true);
    }

    @Bean
    public Binding delayBinding() {
        return BindingBuilder.bind(delayQueue())
                .to(delayExchange())
                .with("order.delay.key");
    }

    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(dlxQueue())
                .to(dlxExchange())
                .with("order.dlx.key");
    }
}

4️⃣ 发送延迟消息(下单逻辑)

java 复制代码
@Service
public class OrderProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendDelayOrderMessage(Long orderId) {
        rabbitTemplate.convertAndSend(
                "order.delay.exchange",
                "order.delay.key",
                orderId,
                message -> {
                    message.getMessageProperties()
                           .setExpiration(String.valueOf(30 * 60 * 1000));
                    return message;
                }
        );
    }
}

💡 面试加分点:TTL 单位是毫秒,可以做到每条消息不同延迟时间


5️⃣ 消费死信,取消订单

java 复制代码
@Component
public class OrderTimeoutConsumer {

    @RabbitListener(queues = "order.dlx.queue")
    public void handleTimeoutOrder(Long orderId) {
        // 查询订单状态
        // if (未支付) {
        //     取消订单(幂等)
        // }
    }
}

四、方案二:延迟消息插件(实战更优)

面试怎么说?

如果公司允许使用插件,生产中我更倾向于 x-delayed-message

因为语义更清晰,实现也更简单。


核心区别

  • 延迟在 交换机层面 完成

  • 使用 x-delay 指定延迟时间

java 复制代码
rabbitTemplate.convertAndSend(
        "order.delay.exchange",
        "order.delay.key",
        orderId,
        message -> {
            message.getMessageProperties()
                   .setHeader("x-delay", 30 * 60 * 1000);
            return message;
        }
);

五、两种方案对比

对比点 死信队列 延迟插件
官方支持
原理复杂度
面试价值 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
项目使用 广泛 非常广泛

六、面试总结话术

RabbitMQ 本身不支持延迟队列,我一般通过死信队列来实现。

下单时发送一条设置 TTL 的消息,过期后进入死信队列,

消费者监听死信队列检查订单状态,未支付则取消。

如果项目允许插件,我会优先使用 x-delayed-message,实现更优雅。

相关推荐
定偶2 小时前
用MySQL玩转数据可视化的技术
数据库·mysql·信息可视化
Remember_9932 小时前
Java 入门指南:从零开始掌握核心语法与编程思想
java·c语言·开发语言·ide·python·leetcode·eclipse
jiayong232 小时前
Tomcat连接器与协议处理面试题
java·tomcat
a程序小傲2 小时前
哈罗Java面试被问:布隆过滤器的误判率和哈希函数选择
java·服务器·算法·面试·职场和发展·哈希算法
lbb 小魔仙2 小时前
【Java】微服务架构 Java 实战:Spring Cloud Gateway + Nacos 全链路搭建指南
java·微服务·架构
十六年开源服务商2 小时前
WordPress多语言支持系统搭建指南
java·大数据·数据库
地球没有花2 小时前
tw引发的对redis的深入了解
数据库·redis·缓存·go
可爱又迷人的反派角色“yang”2 小时前
k8s(七)
java·linux·运维·docker·云原生·容器·kubernetes
填满你的记忆2 小时前
【从零开始——Redis 进化日志|Day6】缓存的三剑客:穿透、击穿、雪崩,到底怎么防?(附生产级代码实战)
java·数据库·redis·缓存·面试