SpringBoot整合RabbitMQ实现死信队列

简介: 前面一文通过[Java整合RabbitMQ实现生产消费(7种通讯方式)](https://wangbinguang.blog.csdn.net/article/details/128284902),本文基于SpringBoot实现RabbitMQ中的死信队列和延迟队列。

前面一文通过Java整合RabbitMQ实现生产消费(7种通讯方式),本文基于SpringBoot实现RabbitMQ中的死信队列和延迟队列。

概念介绍

什么是死信

死信可以理解成没有被正常消费的消息,在RabbitMQ中以下几种情况会被认定为死信:

  1. 消费者使用basic.reject或basic.nack(重新排队参数设置为false)对消息进行否定确认。
  2. 消息到达生存时间还未被消费。
  3. 队列超过长度限制,消息被丢弃。

这些消息会被发送到死信交换机并路由到死信队列中(在RabbitMQ中死信交换机和死信队列就是普通的交换机和队列)。其流转过程如下图
在这里插入图片描述

死信队列应用

  • 作为消息可靠性的一个扩展。比如,在队列已满的情况下也不会丢失消息。
  • 可以实现延迟消费功能。比如,订单15分钟内未支付。

注意事项:基于死信队列实现的延迟消费不适合时间过于复杂的场景。比如,一个队列中第一条消息TTL为10s,第二条消息TTL为5s,由于RabbitMQ只会监听第一条消息,所以本应第二条消息先达到TTL会在第一条消息的TTL之后。对于该现象有两种解决方案:

  1. 维护多个队列,每个队列维护一个TTL时间。
  2. 使用延迟交换机。这种方式需要下载插件支持,参考链接:RabbitMQ插件

    工程搭建

    环境说明

  • RabbitMQ环境,参考RabbitMQ环境搭建
  • Java版本:JDK1.8
  • Maven版本:apache-maven-3.6.3
  • 开发工具:IntelliJ IDEA

    搭建步骤

  1. 创建SpringBoot项目。
  2. pom.xml文件导入RabbitMQ依赖。
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
  1. application.yml文件添加RabbitMQ配置。
spring:
  # rabbitmq配置信息 RabbitProperties类
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    # 开启confirm机制
    publisher-confirm-type: correlated
    # 开启return机制
    publisher-returns: true
    #全局配置,局部配置存在就以局部为准
    listener:
      simple:
        acknowledge-mode: manual # 手动ACK

实现死信

准备Exchange&Queue

@Configuration
public class RabbitMQConfig {
   
   

    /**
     * 正常队列
     */
    public static final String EXCHANGE = "boot-exchange";

    public static final String QUEUE = "boot-queue";

    public static final String ROUTING_KEY = "boot-rout";

    /**
     * 死信队列
     */
    public static final String DEAD_EXCHANGE = "dead-exchange";

    public static final String DEAD_QUEUE = "dead-queue";

    public static final String DEAD_ROUTING_KEY = "dead-rout";

    /**
     * 声明死信交换机
     *
     * @return
     */
    @Bean
    public Exchange deadExchange() {
   
   
        return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
    }

    /**
     * 声明死信队列
     *
     * @return
     */
    @Bean
    public Queue deadQueue() {
   
   
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }


    /**
     * 绑定死信的队列和交换机
     *
     * @param deadExchange
     * @param deadQueue
     * @return
     */
    @Bean
    public Binding deadBind(Exchange deadExchange, Queue deadQueue) {
   
   
        return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTING_KEY).noargs();
    }

    /**
     * 声明交换机,同channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT);
     *
     * @return
     */
    @Bean
    public Exchange bootExchange() {
   
   
        return ExchangeBuilder.directExchange(EXCHANGE).build();
    }

    /**
     * 声明队列,同channel.queueDeclare(QUEUE, true, false, false, null);
     * 绑定死信交换机及路由key
     *
     * @return
     */
    @Bean
    public Queue bootQueue() {
   
   
        return QueueBuilder.durable(QUEUE)
                .deadLetterExchange(DEAD_EXCHANGE)
                .deadLetterRoutingKey(DEAD_ROUTING_KEY)
                //声明队列属性有更改时需要删除队列
                //给队列设置消息时长
                //.ttl(10000)
                //队列最大长度
                .maxLength(1)
                .build();
    }

    /**
     * 绑定队列和交换机,同 channel.queueBind(QUEUE, EXCHANGE, ROUTING_KEY);
     *
     * @param bootExchange
     * @param bootQueue
     * @return
     */
    @Bean
    public Binding bootBind(Exchange bootExchange, Queue bootQueue) {
   
   
        return BindingBuilder.bind(bootQueue).to(bootExchange).with(ROUTING_KEY).noargs();
    }

}

监听死信队列

    @RabbitListener(queues = RabbitMQConfig.DEAD_QUEUE)
    public void listener_dead(String msg, Channel channel, Message message) throws IOException {
   
   
        System.out.println("死信接收到消息" + msg);
        System.out.println("唯一标识:" + message.getMessageProperties().getCorrelationId());
        System.out.println("messageID:" + message.getMessageProperties().getMessageId());
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

方式一——消费者拒绝&否认

  • 拒绝消息
      @RabbitListener(queues = RabbitMQConfig.QUEUE)
      public void listener(String msg, Channel channel, Message message) throws IOException {
         
         
          System.out.println("接收到消息" + msg);
          channel.basicReject(message.getMessageProperties().getDeliveryTag(), false)
      }
    
  • 否认消息
      @RabbitListener(queues = RabbitMQConfig.QUEUE)
      public void listener(String msg, Channel channel, Message message) throws IOException {
         
         
          System.out.println("接收到消息" + msg);
           channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
      }
    

    方式二——超过消息TTL

  • 发送消息时设置TTL

    @SpringBootTest
    public class Publisher {
         
         
    
      @Autowired
      private RabbitTemplate template;
          /**
       * 5秒未被消费会路由到死信队列
       */
      @Test
      public void publish_expir() {
         
         
          template.convertAndSend(RabbitMQConfig.EXCHANGE, RabbitMQConfig.ROUTING_KEY, "hello expir dead", message -> {
         
         
              message.getMessageProperties().setExpiration("5000");
              return message;
          });
      }
    }
    
  • 设置队列所有消息的TTL
    更新RabbitMQConfig类中bootQueue() ,更新后需要删除队列,因为队列属性有更改
      @Bean
      public Queue bootQueue() {
         
         
          return QueueBuilder.durable(QUEUE)
                  .deadLetterExchange(DEAD_EXCHANGE)
                  .deadLetterRoutingKey(DEAD_ROUTING_KEY)
                  //声明队列属性有更改时需要删除队列
                  //给队列设置消息时长
                  .ttl(10000)
                  .build();
      }
    

    方式三——超过队列长度限制

    设置队列长度限制,当队列长度超过设置的阈值,消息便会路由到死信队列。
      @Bean
      public Queue bootQueue() {
         
         
          return QueueBuilder.durable(QUEUE)
                  .deadLetterExchange(DEAD_EXCHANGE)
                  .deadLetterRoutingKey(DEAD_ROUTING_KEY)
                  //声明队列属性有更改时需要删除队列
                  .maxLength(1)
                  .build();
      }
    

    代码仓库

    点我
相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
3月前
|
编解码 NoSQL Java
使用Spring Boot + Redis 队列实现视频文件上传及FFmpeg转码的技术分享
【8月更文挑战第30天】在当前的互联网应用中,视频内容的处理与分发已成为不可或缺的一部分。对于视频平台而言,高效、稳定地处理用户上传的视频文件,并对其进行转码以适应不同设备的播放需求,是提升用户体验的关键。本文将围绕使用Spring Boot结合Redis队列技术来实现视频文件上传及FFmpeg转码的过程,分享一系列技术干货。
200 3
|
3月前
|
消息中间件 Java 网络架构
|
1月前
|
消息中间件 存储 监控
RabbitMQ 队列之战:Classic 和 Quorum 的性能洞察
RabbitMQ 是一个功能强大的消息代理,用于分布式应用程序间的通信。它通过队列临时存储消息,支持异步通信和解耦。经典队列适合高吞吐量和低延迟场景,而仲裁队列则提供高可用性和容错能力,适用于关键任务系统。选择哪种队列取决于性能、持久性和容错性的需求。
120 6
|
2月前
|
消息中间件 JSON Java
|
3月前
|
消息中间件 Java 测试技术
消息中间件RabbitMQ---SpringBoot整合RabbitMQ【三】
这篇文章是关于如何在SpringBoot应用中整合RabbitMQ的消息中间件。内容包括了在SpringBoot项目中添加RabbitMQ的依赖、配置文件设置、启动类注解,以及如何通过单元测试来创建交换器、队列、绑定,并发送和接收消息。文章还介绍了如何配置消息转换器以支持对象的序列化和反序列化,以及如何使用注解`@RabbitListener`来接收消息。
消息中间件RabbitMQ---SpringBoot整合RabbitMQ【三】
|
2月前
|
消息中间件
rabbitmq,&队列
rabbitmq,&队列
|
2月前
|
消息中间件 JSON Java
玩转RabbitMQ声明队列交换机、消息转换器
玩转RabbitMQ声明队列交换机、消息转换器
84 0
|
3月前
|
消息中间件 存储 NoSQL
MQ的顺序性保证:顺序队列、消息编号、分布式锁,一文全掌握!
【8月更文挑战第24天】消息队列(MQ)是分布式系统的关键组件,用于实现系统解耦、提升可扩展性和可用性。保证消息顺序性是其重要挑战之一。本文介绍三种常用策略:顺序队列、消息编号与分布式锁,通过示例展示如何确保消息按需排序。这些方法各有优势,可根据实际场景灵活选用。提供的Java示例有助于加深理解与实践应用。
84 2
|
3月前
|
网络协议 Java 物联网
MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图
MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图
766 2
|
3月前
|
消息中间件 Java Maven