SpringBoot 整合 RabbitMQ 实现延时消息实战(死信队列方案)

举报
JiHua 发表于 2026/11/06 17:56:26 2026/11/06
【摘要】 在业务开发中经常遇到延时场景,例如订单超时自动关闭、预约到期提醒、消息延迟推送。本文基于SpringBoot + RabbitMQ,使用死信队列实现延时消息,完整讲解交换机、队列绑定、消息TTL、死信转发逻辑,附带可直接运行的代码,同时对比延时插件方案优缺点,给出生产环境落地建议。

前言

很多业务场景需要延时能力:用户下单后30分钟未支付,自动关闭订单;活动预约到期提醒;定时重试失败任务。
RabbitMQ原生没有直接的延时消息,主流两种实现:

  1. 死信队列(DLX + TTL):不需要额外装插件,兼容性好,适合中小项目,也是最常用方案
  2. rabbitmq-delayed-message-exchange 延时插件:原生延时交换机,使用更简单,但需要运维安装插件,云厂商部分MQ实例不支持

本文采用死信队列方案,开箱即用,适合华为云RabbitMQ服务直接部署。

原理说明

  1. 消息发送到普通业务队列,给消息设置TTL(存活时间)
  2. 消息超时没有被消费,变成死信(Dead Letter)
  3. 普通队列绑定死信交换机,死信自动转发到死信交换机
  4. 死信交换机路由到延时消费队列
  5. 消费者监听延时消费队列,到达时间后执行业务逻辑

注意:TTL是队列级别或者消息级别,队列级TTL:整个队列所有消息超时时间一样;消息级TTL可以每条消息单独设置。队列级TTL性能更好。

环境依赖

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

application.yml 配置

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    # 消息确认
    publisher-confirm-type: correlated
    publisher-returns: true
    listener:
      simple:
        acknowledge-mode: manual # 手动ACK,防止消息丢失

核心配置类

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 NORMAL_EXCHANGE = "normal.order.exchange";
    // 普通队列(消息在这里等待超时)
    public static final String NORMAL_QUEUE = "normal.order.queue";
    // 普通队列路由key
    public static final String NORMAL_ROUTING_KEY = "order.create";

    // 死信交换机 DLX
    public static final String DLX_EXCHANGE = "dlx.order.exchange";
    // 死信消费队列(延时后真正消费的队列)
    public static final String DLX_QUEUE = "dlx.order.queue";
    public static final String DLX_ROUTING_KEY = "order.delay";

    // 1. 声明普通交换机
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(NORMAL_EXCHANGE).durable(true).build();
    }

    // 2. 声明死信交换机
    @Bean
    public DirectExchange dlxExchange() {
        return ExchangeBuilder.directExchange(DLX_EXCHANGE).durable(true).build();
    }

    // 3. 普通队列,绑定死信交换机 + TTL 30分钟(单位ms)
    @Bean
    public Queue normalQueue() {
        Map<String, Object> args = new HashMap<>();
        // 设置死信交换机
        args.put("x-dead-letter-exchange", DLX_EXCHANGE);
        // 设置死信路由key
        args.put("x-dead-letter-routing-key", DLX_ROUTING_KEY);
        // 队列TTL:30分钟
        args.put("x-message-ttl", 30 * 60 * 1000);
        return QueueBuilder.durable(NORMAL_QUEUE).withArguments(args).build();
    }

    // 4. 死信消费队列
    @Bean
    public Queue dlxQueue() {
        return QueueBuilder.durable(DLX_QUEUE).build();
    }

    // 绑定:普通交换机 <-> 普通队列
    @Bean
    public Binding normalBinding() {
        return BindingBuilder.bind(normalQueue()).to(normalExchange()).with(NORMAL_ROUTING_KEY);
    }

    // 绑定:死信交换机 <-> 死信队列
    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(DLX_ROUTING_KEY);
    }
}

生产者:发送延时消息

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;

@Component
public class OrderDelayProducer {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void sendOrderDelayMsg(String orderNo) {
        rabbitTemplate.convertAndSend(
                RabbitDelayConfig.NORMAL_EXCHANGE,
                RabbitDelayConfig.NORMAL_ROUTING_KEY,
                orderNo
        );
        System.out.println("发送延时消息,订单号:" + orderNo + ",30分钟后自动关闭");
    }
}

消费者:监听延时队列,处理超时订单

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import java.io.IOException;

@Component
public class OrderDelayConsumer {

    @RabbitListener(queues = RabbitDelayConfig.DLX_QUEUE)
    public void consume(String orderNo, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
        try {
            // 业务逻辑:查询订单状态,如果未支付,关闭订单、释放库存
            System.out.println("收到延时消息,执行订单超时关闭:" + orderNo);
            // TODO 调用订单Service:orderService.closeExpireOrder(orderNo);

            // 手动ACK,确认消费成功
            channel.basicAck(tag, false);
        } catch (Exception e) {
            // 消费失败,NACK,重回队列或者转入人工补偿队列
            channel.basicNack(tag, false, true);
            e.printStackTrace();
        }
    }
}

测试接口

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.UUID;

@RestController
public class OrderTestController {

    @Resource
    private OrderDelayProducer producer;

    @GetMapping("/sendDelayMsg")
    public String send(@RequestParam(required = false) String orderNo) {
        if(orderNo == null) {
            orderNo = UUID.randomUUID().toString();
        }
        producer.sendOrderDelayMsg(orderNo);
        return "消息发送成功,orderNo:" + orderNo;
    }
}

踩坑重点(生产必看)

  1. 死信队列方案短板:如果队列设置统一TTL,消息是排队超时。比如第一条消息1小时TTL,第二条10分钟TTL,第二条要等第一条超时之后才会被消费!不同延时时间不能放同一个队列。
    ✅ 解决方案:按TTL分组创建多个队列,或者改用延时插件。
  2. 消息丢失风险:一定要开启手动ACK;消息确认回调做好日志记录。
  3. 订单状态校验:延时消息到达后,必须先查库,如果订单已经支付,直接忽略这条消息,不能直接关闭。
  4. 幂等:消费者要做幂等,防止消息重复投递导致重复关闭订单(可以用订单状态、Redis标记)。
  5. 不要把TTL设置极大,堆积大量死信会加重MQ压力。

方案对比

方案 优点 缺点 适用场景
死信队列DLX 无需插件、云MQ通用、稳定 同队列消息TTL会阻塞,多延时需要多队列 延时时间固定、变化少的业务
延时插件Exchange 支持每条消息自定义延时,顺序不受影响 需要运维安装插件,部分云厂商不支持 延时时间灵活、多样化

总结

死信队列是RabbitMQ延时消息最经典的实现方式,上手简单,不需要额外运维改造,适合订单超时这类固定延时场景。核心就是利用消息过期后自动转发死信的特性。
开发中最容易踩的坑就是同队列不同TTL消息阻塞,这个点一定要在业务设计时规避。如果业务里延时时间非常零散多变,优先考虑延时插件方案,或者改用Redis ZSet轮询的延时方案。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。