Spring Boot4.0整合RabbitMQ死信队列详解

Spring Boot整合RabbitMQ死信队列详解

为啥那么讲解死信队列,因为好多人不会使用,不知道什么场景下使用,此案例是我在公司实现的一种方式,让大家都可以学习到

一、死信队列的好处

1. 提高系统可靠性

  • 避免消息丢失,确保处理失败的消息有备份
  • 防止因消息处理异常导致的消息无限重试

2. 异常消息管理

  • 将异常消息与正常消息分离
  • 便于监控和排查问题消息

3. 灵活的重试机制

  • 支持延迟重试
  • 可设置不同的重试策略

4. 系统解耦

  • 业务逻辑与异常处理逻辑分离
  • 提高代码的可维护性

二、注解式配置说明

1. 主配置注解

typescript 复制代码
@Configuration
public class RabbitMQConfig {
    
    // 主队列
    @Bean
    public Queue orderQueue() {
        return QueueBuilder.durable("order.queue")
            .deadLetterExchange("dlx.exchange")  // 死信交换器
            .deadLetterRoutingKey("dlx.routing.key")  // 死信路由键
            .ttl(10000)  // 消息10秒未消费进入死信
            .maxLength(1000)  // 队列最大长度
            .build();
    }
    
    // 死信队列
    @Bean
    public Queue deadLetterQueue() {
        return QueueBuilder.durable("dl.queue")
            .build();
    }
    
    // 死信交换器
    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange("dlx.exchange");
    }
    
    // 绑定死信交换器和队列
    @Bean
    public Binding deadLetterBinding() {
        return BindingBuilder.bind(deadLetterQueue())
            .to(deadLetterExchange())
            .with("dlx.routing.key");
    }
}

2. 监听器注解

typescript 复制代码
@Component
public class OrderMessageListener {
    
    // 监听正常队列
    @RabbitListener(queues = "order.queue")
    public void processOrderMessage(OrderDTO order, 
                                   Channel channel, 
                                   @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
        try {
            // 业务处理逻辑
            if (processOrder(order)) {
                // 手动确认
                channel.basicAck(tag, false);
            } else {
                // 拒绝消息,进入死信队列
                channel.basicNack(tag, false, false);
            }
        } catch (Exception e) {
            // 异常时拒绝
            channel.basicNack(tag, false, false);
        }
    }
    
    // 监听死信队列
    @RabbitListener(queues = "dl.queue")
    public void processDeadLetter(OrderDTO order) {
        log.error("收到死信消息: {}", order);
        // 死信消息处理逻辑
        handleDeadLetter(order);
    }
}

三、详细整合步骤

1. 添加依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2. 配置属性

yaml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    # 开启消息返回机制
    publisher-returns: true
    # 开启确认机制
    publisher-confirm-type: correlated
    listener:
      simple:
        # 手动确认
        acknowledge-mode: manual
        # 重试配置
        retry:
          enabled: true
          max-attempts: 3
          initial-interval: 1000

3. 完整配置类

typescript 复制代码
@Configuration
@Slf4j
public class RabbitMQFullConfig {
    
    // ========== 正常业务队列配置 ==========
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange("order.exchange", true, false);
    }
    
    @Bean
    public Queue orderQueue() {
        Map<String, Object> args = new HashMap<>();
        // 死信交换器
        args.put("x-dead-letter-exchange", "order.dlx.exchange");
        // 死信路由键
        args.put("x-dead-letter-routing-key", "order.dlx.key");
        // 消息TTL(毫秒)
        args.put("x-message-ttl", 30000);
        // 队列最大长度
        args.put("x-max-length", 10000);
        return QueueBuilder.durable("order.queue")
            .withArguments(args)
            .build();
    }
    
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue())
            .to(orderExchange())
            .with("order.key");
    }
    
    // ========== 死信队列配置 ==========
    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange("order.dlx.exchange", true, false);
    }
    
    @Bean
    public Queue deadLetterQueue() {
        return QueueBuilder.durable("order.dl.queue")
            .build();
    }
    
    @Bean
    public Binding deadLetterBinding() {
        return BindingBuilder.bind(deadLetterQueue())
            .to(deadLetterExchange())
            .with("order.dlx.key");
    }
    
    // ========== 重试队列(延时队列替代方案)==========
    @Bean
    public CustomExchange delayExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange("delay.exchange", 
            "x-delayed-message", true, false, args);
    }
    
    @Bean
    public Queue delayQueue() {
        return QueueBuilder.durable("delay.queue")
            .build();
    }
    
    @Bean
    public Binding delayBinding() {
        return BindingBuilder.bind(delayQueue())
            .to(delayExchange())
            .with("delay.key")
            .noargs();
    }
}

4. 消息生产者

java 复制代码
@Component
@Slf4j
public class MessageProducer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    // 发送普通消息
    public void sendOrderMessage(OrderDTO order) {
        CorrelationData correlationData = new CorrelationData(order.getId());
        
        rabbitTemplate.convertAndSend(
            "order.exchange",
            "order.key",
            order,
            message -> {
                // 设置消息属性
                message.getMessageProperties()
                    .setExpiration("30000")  // 消息TTL
                    .setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                return message;
            },
            correlationData
        );
        
        // 确认回调
        correlationData.getFuture().addCallback(
            result -> {
                if (result.isAck()) {
                    log.info("消息发送成功: {}", order.getId());
                }
            },
            ex -> log.error("消息发送失败: {}", ex.getMessage())
        );
    }
    
    // 发送延迟消息
    public void sendDelayMessage(OrderDTO order, int delayTime) {
        rabbitTemplate.convertAndSend(
            "delay.exchange",
            "delay.key",
            order,
            message -> {
                message.getMessageProperties()
                    .setHeader("x-delay", delayTime);
                return message;
            }
        );
    }
}

5. 消息消费者(完整版)

less 复制代码
@Component
@Slf4j
public class OrderMessageConsumer {
    
    private static final int MAX_RETRY_COUNT = 3;
    
    @Autowired
    private MessageProducer messageProducer;
    
    /**
     * 监听订单队列
     */
    @RabbitListener(queues = "order.queue")
    public void handleOrderMessage(
            @Payload OrderDTO order,
            @Headers Map<String, Object> headers,
            Channel channel,
            @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        
        try {
            log.info("收到订单消息: {}", order);
            
            // 模拟业务处理
            boolean success = processOrderBusiness(order);
            
            if (success) {
                // 业务成功,确认消息
                channel.basicAck(deliveryTag, false);
                log.info("订单处理成功: {}", order.getId());
            } else {
                // 获取重试次数
                Integer retryCount = (Integer) headers.get("x-retry-count");
                retryCount = (retryCount == null) ? 1 : retryCount + 1;
                
                if (retryCount <= MAX_RETRY_COUNT) {
                    // 重试次数未超限,重新入队
                    log.warn("订单处理失败,第{}次重试: {}", retryCount, order.getId());
                    
                    // 设置重试计数
                    headers.put("x-retry-count", retryCount);
                    
                    // 延迟重试
                    messageProducer.sendDelayMessage(order, 5000);
                    
                    // 确认消息,避免重新投递
                    channel.basicAck(deliveryTag, false);
                } else {
                    // 超过重试次数,进入死信队列
                    log.error("订单处理失败次数超过上限,进入死信队列: {}", order.getId());
                    channel.basicNack(deliveryTag, false, false);
                }
            }
        } catch (Exception e) {
            log.error("处理订单消息异常: {}", e.getMessage());
            try {
                // 拒绝消息,进入死信队列
                channel.basicNack(deliveryTag, false, false);
            } catch (IOException ex) {
                log.error("拒绝消息失败: {}", ex.getMessage());
            }
        }
    }
    
    /**
     * 监听死信队列
     */
    @RabbitListener(queues = "order.dl.queue")
    public void handleDeadLetterMessage(
            @Payload OrderDTO order,
            @Headers Map<String, Object> headers) {
        
        log.error("收到死信消息: {}", order);
        
        // 记录死信消息
        logDeadLetter(order, headers);
        
        // 发送告警
        sendAlert(order);
        
        // 人工处理或其他补偿措施
        manualProcess(order);
    }
    
    /**
     * 监听延迟队列
     */
    @RabbitListener(queues = "delay.queue")
    public void handleDelayMessage(@Payload OrderDTO order) {
        log.info("收到延迟消息,开始重试: {}", order);
        
        // 重新发送到订单队列
        messageProducer.sendOrderMessage(order);
    }
    
    private boolean processOrderBusiness(OrderDTO order) {
        // 业务处理逻辑
        // 返回true表示成功,false表示失败
        return new Random().nextBoolean();
    }
    
    private void logDeadLetter(OrderDTO order, Map<String, Object> headers) {
        // 记录死信日志
        log.info("记录死信: {}, headers: {}", order, headers);
    }
    
    private void sendAlert(OrderDTO order) {
        // 发送告警通知
        log.warn("发送告警: 订单{}处理失败", order.getId());
    }
    
    private void manualProcess(OrderDTO order) {
        // 人工处理逻辑
        log.info("等待人工处理订单: {}", order.getId());
    }
}

四、使用场景

1. 订单超时取消

typescript 复制代码
// 订单创建时发送延迟消息
public void createOrder(OrderDTO order) {
    // 保存订单
    orderService.save(order);
    
    // 发送30分钟过期的消息
    rabbitTemplate.convertAndSend(
        "order.exchange",
        "order.key",
        order,
        message -> {
            message.getMessageProperties()
                .setExpiration("1800000");  // 30分钟
            return message;
        }
    );
}

2. 支付回调重试

typescript 复制代码
// 支付回调失败时进入死信队列,人工处理
@RabbitListener(queues = "payment.callback.queue")
public void handlePaymentCallback(PaymentDTO payment) {
    if (!paymentService.processCallback(payment)) {
        throw new RuntimeException("支付回调处理失败");
    }
}

3. 库存锁定与释放

typescript 复制代码
// 库存锁定15分钟后自动释放
public void lockInventory(String orderId) {
    inventoryService.lock(orderId);
    
    // 发送15分钟后到期的消息
    rabbitTemplate.convertAndSend(
        "inventory.exchange",
        "inventory.lock.key",
        orderId,
        message -> {
            message.getMessageProperties()
                .setExpiration("900000");  // 15分钟
            return message;
        }
    );
}

4. 消息重试机制

kotlin 复制代码
// 分级重试策略
public class RetryStrategy {
    // 第一次重试:5秒后
    // 第二次重试:30秒后
    // 第三次重试:5分钟后
    // 超过3次进入死信队列
}

五、优点总结

  1. 可靠性:确保消息不丢失,即使处理失败也有备份
  2. 灵活性:支持多种死信策略(超时、长度限制、拒绝等)
  3. 可维护性:异常处理与正常业务逻辑分离
  4. 监控性:死信队列便于监控和统计异常消息
  5. 可扩展性:支持多种重试和补偿机制

六、最佳实践建议

  1. 合理设置TTL:根据业务需求设置合适的过期时间
  2. 监控死信队列:设置告警,及时处理死信消息
  3. 限制队列大小:防止消息积压
  4. 记录详细日志:便于问题排查
  5. 死信消息分析:定期分析死信原因,优化系统

通过Spring Boot整合RabbitMQ死信队列,可以构建更加健壮、可靠的消息驱动系统,有效处理各种异常场景,提高系统的整体稳定性。

相关推荐
独自归家的兔2 小时前
大模型通义千问3-VL-Plus - QVQ 视觉推理模型
java·人工智能·intellij-idea
nnsix2 小时前
Unity ReferenceFinder插件 窗口中选择资源时 同步选择Assets下的资源
java·unity·游戏引擎
天天摸鱼的java工程师2 小时前
🚪单点登录实战:同端同账号互踢下线的最佳实践(Java 实现)
java·后端
Kiri霧2 小时前
Go 结构体
java·开发语言·golang
小飞Coding2 小时前
Java堆外内存里的“密文”--从内存内容反推业务模块实战
jvm·后端
狂奔小菜鸡2 小时前
Day29 | Java集合框架之Map接口详解
java·后端·java ee
踏浪无痕2 小时前
告别手写 TraceId!Micrometer 链路追踪在 Spring Boot 中的落地实践
后端·spring cloud·架构
爱学习的小可爱卢2 小时前
JavaEE进阶——Spring事务与传播机制实战指南
java·java-ee·事务
-大头.2 小时前
Java泛型实战:类型安全与高效开发
java·开发语言·安全