Skip to content

RabbitMQ延迟队列

RabbitMQ 常见的延迟队列实现方式是 TTL + 死信交换机。消息先进入一个没有消费者的延迟队列,等待 TTL 过期后变成死信,再由死信交换机路由到真正的业务队列。

使用场景

  • 订单 30 分钟未支付自动关闭。
  • 优惠券到期前提醒。
  • 延迟发送短信或邮件。
  • 失败任务隔一段时间后重试。

TTL + DLX 流程

mermaid
flowchart TD
    A["生产者发送延迟消息"] --> B["延迟交换机"]
    B --> C["延迟队列<br/>设置 TTL"]
    C -->|TTL 到期| D["死信交换机 DLX"]
    D --> E["业务队列"]
    E --> F["消费者处理"]

为什么要经过死信交换机

RabbitMQ 队列中的消息过期后不会自动投递给消费者,而是会变成死信。只有给队列配置了死信交换机,过期消息才会被重新路由到业务队列。

所以延迟队列的关键不是“消费者睡眠”,而是让 Broker 负责延迟投递。

队列 TTL 和消息 TTL

类型说明适合场景
队列 TTL队列里所有消息使用同一个过期时间固定延迟,例如 30 分钟关单
消息 TTL每条消息单独设置过期时间不同消息延迟时间不同

注意:使用普通队列做不同 TTL 的消息延迟时,可能受到队头阻塞影响。第一条消息未过期时,后面的短 TTL 消息可能不能及时投递。

延迟插件

RabbitMQ 也可以通过延迟消息插件实现更直接的延迟投递。插件方式使用起来更简单,但需要额外安装和维护插件,生产环境要确认版本兼容性。

mermaid
flowchart TD
    A["生产者"] --> B["延迟交换机插件"]
    B -->|到期后投递| C["业务队列"]
    C --> D["消费者"]

开发建议

  1. 延迟任务必须有业务兜底。例如订单关闭任务执行时,先查订单是否仍未支付。
  2. 延迟消息可能重复投递,消费端要幂等。
  3. 延迟时间很长、数据量很大时,要评估 Broker 堆积压力。
  4. 对关单、退款、补偿这类关键任务,要增加定时扫描兜底。
  5. 死信交换机和业务交换机命名要清晰,避免路由配置错误。

和 RocketMQ 延迟消息的区别

RocketMQ 通常直接提供延迟或定时消息能力;RabbitMQ 更常用 TTL + DLX 组合实现。RocketMQ 使用上更直接,RabbitMQ 的组合方式更灵活,但配置项更多。

配置 Demo:TTL + DLX 延迟队列

下面配置表示:消息先进入 order.close.delay.queue,30 分钟后过期,再通过死信交换机进入 order.close.queue

java
@Bean
Queue orderCloseDelayQueue() {
    return QueueBuilder.durable("order.close.delay.queue")
        .ttl(30 * 60 * 1000)
        .deadLetterExchange("order.close.dlx")
        .deadLetterRoutingKey("order.close")
        .build();
}

@Bean
DirectExchange orderCloseDlx() {
    return new DirectExchange("order.close.dlx", true, false);
}

@Bean
Queue orderCloseQueue() {
    return QueueBuilder.durable("order.close.queue").build();
}

@Bean
Binding orderCloseBinding() {
    return BindingBuilder.bind(orderCloseQueue())
        .to(orderCloseDlx())
        .with("order.close");
}

消费者收到关单消息后,必须先查询订单状态:只有订单仍是待支付,才允许关闭。