在实际项目和面试中,RabbitMQ 延迟消息几乎是一个必问点,尤其常常和下面这个场景绑定出现:
订单超时未支付,如何自动取消?
很多同学:
-
知道用 MQ
-
知道死信队列
-
但一问原理就说不清
本文将以 "面试官视角" + "真实项目设计" 的方式,系统讲清:
-
RabbitMQ 为什么不直接支持延迟队列
-
延迟消息的 两种主流实现方案
-
订单超时取消的 完整设计 + Java 代码
读完这篇文章,你至少能在面试中:
✅ 把延迟消息讲清楚
✅ 把订单超时方案说明白
✅ 写出能落地的代码
一、面试引入:订单超时关闭你是怎么做的?
这是一个非常经典的后端面试题,本质考察三点:
-
你有没有真实做过业务
-
你对 MQ 的理解是不是停留在 API 层
-
你是否具备系统设计能力
业务规则(标准描述)
-
用户下单,生成订单(状态:待支付)
-
给用户 30 分钟支付时间
-
超过 30 分钟仍未支付,订单自动取消
❌ 错误 / 低分答案
-
用定时任务每分钟扫数据库
-
用 while + sleep 轮询
✅ 高分答案方向
下单时发送一条延迟消息,30 分钟后自动检查订单状态,未支付则取消。
这就是 RabbitMQ 延迟消息的典型应用。
二、RabbitMQ 延迟消息的两种实现方案(面试重点)
面试常问:
RabbitMQ 支持延迟队列吗?
标准回答:
RabbitMQ 本身不直接支持延迟队列 ,但可以通过 死信队列 或 延迟消息插件 实现。
| 方案 | 是否官方 | 面试推荐度 | 特点 |
|---|---|---|---|
| 死信交换机(TTL + DLX) | ✅ | ⭐⭐⭐⭐⭐ | 原理题必问 |
| 延迟消息插件 | ❌(插件) | ⭐⭐⭐⭐ | 实战更优 |
三、方案一:死信队列实现延迟消息(必须会)

1️⃣ 面试官最想听到的原理
一句话总结:
给消息设置 TTL,过期后进入死信交换机,再由消费者处理。
完整流程:
-
下单后发送消息到「延迟队列」
-
消息设置 TTL = 30 分钟
-
消息过期,成为死信
-
死信被路由到死信队列
-
消费者监听死信队列,检查并取消订单
如果你能把这 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,实现更优雅。
