Rabbit MQ

目录

什么是消息队列

消息队列组件

RabbitMQ

安装

直连模式-可视化界面

直连模式-Java操作

直连模式-Spring Boot操作

Spring Boot操作

普通消费 

消费并反馈

Json消息

工作队列模式

工作队列模式

先监听再发消息

先发消息再监听

发布订阅模式

路由模式

主题模式

头部模式

死信队列

概述

产生死信

消息被拒绝

消息超时未消费

消息队列达到最大长度


什么是消息队列

进行大量的远程调用时,传统的Http方式容易造成阻塞,所以引入了消息队列的概念,即让消息排队,按照队列进行消费。

它能够将发送方发送的信息放入队列中,当新的消息入队时,会通知接收方进行处理,一般消息发送方称为生产者,接收方称为消费者

这样所有的请求,都可以丢到消息队列中,再由消费者取出,不再是直接连接消费者的形式了,而是加了一个中间件,这是一种很好的解耦方案。并且在高并发的情况下,由于消费者能力有限,消息队列也能起到一个削峰填谷的作用,堆积一部分的请求,再由消费者来慢慢处理,而不会像直接调用那样请求蜂拥而至。

消息队列组件

ActiveMQ、RabbitMQ、RocketMQ、Kafka是目前主流的消息队列组件。它们的特点如下:

综合以上因素,这里选择RabbitMQ作为消息队列组件进行演示。原因如下:

  • 吞吐量虽然不高,但中小型项目完全足够。
  • 时效性强。延迟达到微妙级别,较同类产品延迟最低。
  • 可用性强。提供了完善的高可用实现机制。
  • 可靠性强。基本不会丢包。
  • 并发能力强。基于Erlang开发,提供了丰富的交换机功能,性能好、延时低。
  • 开源免费。没有使用成本,且社区活跃度高,产品稳定。

消息队列组件的功能和实现是大同小异的,学习好一个组件就可以做到一通百通,而且还有Spring Cloud Stream这样的消息队列连接组件,可以屏蔽各消息队列组件的差异性,使用统一的标准调用消息队列组件。

所以,想要掌握消息队列,从RabbitMQ入手是一个比较好的选择。

RabbitMQ

官方网站:https://www.rabbitmq.com

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。

Rabbit科技有限公司开发了RabbitMQ,并提供对其的支持。

  • 起初,Rabbit科技是LSHIFT和CohesiveFT在2007年成立的合资企业。
  • 2010年4月被VMware旗下的SpringSource收购。
  • RabbitMQ在2013年5月成为GoPivotal的一部分。

其特点如下:

  • 拥有数万计的用户,是最受欢迎的开源消息队列之一,从T-Mobile到Runtastic,RabbitMQ在全球范围内用于小型初创企业和大型企业。
  • 轻量级,易于在本地和云端部署,它支持多种消息协议。
  • 可以部署在分布式和联合配置中,以满足大规模、高可用性要求。
  • 在许多操作系统和云环境中运行,并为大多数流行语言提供了广泛的开发者工具

运行过程:

  • 生产者(Publisher):生产消息的终端。
  • 信道(Channel):服务端和客户端连接都会使用一个Channel,再通过Channel去访问到RabbitMQ服务器。这里的通信协议不是http,而是amqp协议。
  • 虚拟主机(Virtual Host):类似于环境隔离,不同环境都可以单独配置一个Virtual Host,每个Virtual Host可以包含很多个Exchange和Queue,每个Virtual Host相互之间不影响。
  • 交换机Exchange):根据请求,转发给相应的消息队列,每个队列都可以绑定到Exchange上,这样Exchange就可以将数据转发给队列了,可以存在很多个,不同的Exchange类型可以用于实现不同消息的模式。
  • 消息队列(Queue):消息队列本体,生产者所有的消息都存放在消息队列中,等待消费者取出。
  • 消费者(Consumer):消费消息的终端。

安装

推荐使用Docker进行安装。

  1. 执行如下命令。
docker run -d --restart always --name rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management

参数解释:
- `-d`:设置程序后台运行。
- `--restart always`:设置服务自启动。
- `--name`:指定运行后的容器名称。
- `-e`:设置环境。
    这里主要设置账号密码为admin。
    默认账号密码为guest ,只能在 localhost访问,由于需要外网访问,所以创建admin用户进行登录。
- `-p`:设置公网IP地址的端口号对应容器内部的端口号。
- `rabbitmq:management`:安装可视化管理组件。

  1. 访问可视化界面。

    访问地址:服务器IP:15672

    这里以本地访问为例:

  2. 使用设置的账号密码登录。

    这里使用admin/admin进行登录,登录后的界面为:


  • 环境

    Ubuntu 22.04

    RabbitMQ 3.11.10

    Erlang 25.3

直连模式-可视化界面

这里先演示最简单的模型:直连模式。其结构图为:

一个生产者 -> 消息队列 -> 一个消费者

生产者只需要将数据丢进消息队列,而消费者只需要将数据从消息队列中取出,这样就实现了生产者和消费者的消息交互。

  1. 创建一个新的实验环境,即新建一个Virtual Host。

  2. 添加新的虚拟主机之后,我们可以看到,当前admin用户的主机访问权限中新增了刚刚添加的环境。

  3. 查看交换机。

    交换机列表中自动新增了刚刚创建好的虚拟主机相关的预设交换机,一共7个。

    这里首先介绍一下前面两个direct类型的交换机,一个是(AMQP default)还有一个是amq.direct,它们都是直连模式的交换机。

    • 单击(AMQP default)进入详情。

      • (AMQP default)是所有虚拟主机都会自带的一个默认交换机。
      • 此交换机默认绑定到所有的消息队列。

        如果是通过默认交换机发送消息,会根据消息的routingKey(发消息时指定)决定发送给哪个同名的消息队列,同时也不能显式地将消息队列绑定或解绑到此交换机。

      • 此交换机不可删除。

        可以看到,详细信息中,特征(Features)项为:durable:true,表明当前交换机特性是持久化的,也就是说就算机器重启,此交换机也会保留;如果不是持久化,那么一旦重启就会消失。

        在列表中看到D的字样,就表示此交换机是持久化的。

        所有自动生成的交换机都是持久化的。

    • 单击amq.direct进入详情。

      这个交换机和默认交换机类型一致,并且也是持久化的。

      但是可以看到它是具有绑定关系的,如果没有指定的消息队列绑定到此交换机上,那么这个交换机无法正常将信息存放到指定的消息队列中,也是根据routingKey寻找消息队列(可以自定义)。

  4. 查看消息队列。目前没有消息队列,所以需要创建一个。

    • Virtual host:虚拟主机。这里选择自建的,在这个虚拟主机下创建此消息队列。
    • Type:类型。选择Classic,也就是经典类型。
    • Name:名称。可以随便取,这里取test。
    • Durability:持久化。这里选择Durable,即持久的。
    • Auto delete:自动删除。这里选择No,如果选Yes,即代表需要至少有一个消费者连接到这个队列,一旦所有与这个队列连接的消费者都断开时,就会自动删除此队列。
    • Arguments:参数。暂时不用设置。
  5. 点击创建的消息队列名称,可查看详情。

    详细信息中包括队列的当前负载状态、属性、消息队列占用的内存,消息数量等。

    从绑定信息可以发现,该队列默认绑定了交换机,就是前面介绍的(AMQP default)默认交换机。

    现在需要将此消息队列绑定到amq.direct,这样就可以通过此交换机向此消息队列发送消息了:

  6. 回到交换机。这里也显示了与队列的绑定关系。

    向该消息队列中发送一条消息:

  7. 回到队列。

    可以看到已经有一条消息了:

  8. 获取消息。

    选择Get messages,可以获取消息:

    • Ack Mode:接收消息的模式。有4种,分别为:

      • Nack message requeue true:获取到消息的内容;不会去消费消息。
      • Automatic ack:获取到消息的内容;会去消费消息。
      • Reject requeue true:拒绝获取消息;消息重新入队。
      • Reject requeue false:拒绝获取消息;消息不重新入队,将会被删除。

        这里使用默认的即第1种就可以了,这样只会查看消息,但是不会取出,消息依然存在于消息队列中。

    • Encoding:编码格式。使用默认的就可以。
    • Messages:要生效的操作数量。选择1就行。

      消息已经成功读取到。

  9. 除了在交换机发送消息给消息队列之外,也可以直接在消息队列这里发。

    • Delivery mode:推送方式。
      • Non-persistent:不持久化。如果服务器重启,此消息删除。
      • Persistent:持久化。如果服务器重启,此消息依然存在。
    • Headers:头部信息。
    • Properties:属性。
    • Payload:载荷的消息。
  10. 如果不需要再使用此消息队列了,可以手动对其进行删除或是清空。

  • 环境
    • Ubuntu 22.04
    • RabbitMQ 3.11.10
    • Erlang 25.3

直连模式-Java操作

使用Java原生的方式使用RabbitMQ现在已经较少,但这是基础,还是有必要了解的。

1.引入依赖。

 <dependency>
     <groupId>com.rabbitmq</groupId>
     <artifactId>amqp-client</artifactId>
     <version>5.16.0</version>
 </dependency>

2.实现生产者。

 // 使用ConnectionFactory创建连接
 ConnectionFactory factory = new ConnectionFactory();
 // 设置连接信息
 factory.setHost("127.0.0.1");
 // 注意这里写5672,是amqp协议端口
 factory.setPort(5672);
 factory.setUsername("admin");
 factory.setPassword("admin");
 factory.setVirtualHost("/test");
 // 创建Connection
 Connection connection = factory.newConnection();
 // 通过Connection创建新的Channel
 Channel channel = connection.createChannel();
 try (connection; channel) {
     // 声明队列,如果此队列不存在,会自动创建
     channel.queueDeclare("test_java", false, false, false, null);
     // 将队列绑定到交换机
     channel.queueBind("test_java", "amq.direct", "test_java_key");
     // 转换为byte[],发布消息
     channel.basicPublish("amq.direct", "test_java_key", null, "Hello World!".getBytes());
 }

  • queueDeclare方法参数如下:

    • queue:队列的名称(默认创建后routingKey和队列名称一致)

    • durable:是否持久化。

    • exclusive:是否排他。

      如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。

      排他队列是基于Connection可见,同一个Connection的不同Channel是可以同时访问同一个连接创建的排他队列。

      如果一个Connection已经声明了一个排他队列,其他的Connection是不允许建立同名的排他队列的。

      即使该队列是持久化的,一旦Connection关闭或者客户端退出,该排他队列都会自动被删除。

    • autoDelete:是否自动删除。

    • arguments:设置队列的其他一些参数,这里暂时不需要。

  • queueBind方法参数如下:

    • queue:需要绑定的队列名称。
    • exchange:需要绑定的交换机名称。
    • routingKey:路由标识。
  • basicPublish方法参数如下:

    • exchange:对应的Exchange名称,这里就使用第二个直连交换机。
    • routingKey:这里填写绑定时指定的routingKey,其实和之前在管理页面操作一样。
    • props:其他的配置。
    • body:消息本体。
      执行完成后,可以在管理页面中看到刚刚创建好的消息队列:

    并且此消息队列已经成功与amq.direct交换机进行绑定:

3.实现消费者。

 // 使用ConnectionFactory创建连接
 ConnectionFactory factory = new ConnectionFactory();
 // 设置连接信息
 factory.setHost("127.0.0.1");
 // 注意这里写5672,是amqp协议端口
 factory.setPort(5672);
 factory.setUsername("admin");
 factory.setPassword("admin");
 factory.setVirtualHost("/test");
 // 这里不使用try-with-resource,因为消费者是一直等待新的消息到来,然后按照设定的逻辑进行处理,所以这里不能在定义完成之后就关闭连接
 // 创建Connection
 Connection connection = factory.newConnection();
 // 通过Connection创建新的Channel
 Channel channel = connection.createChannel();
 // 创建一个基本的消费者
 channel.basicConsume("test_java", false, (s, delivery) -> {
     System.out.println(new String(delivery.getBody()));
     /*
     确认应答
     参数 1:当前的消息标签
     参数 2:是否批量处理消息队列中所有的消息,如果为false表示只处理当前消息
      */
     channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
     /*
     拒绝应答
     参数 1:当前的消息标签
     参数 2:倍数
     参数 3:是否将当前消息放回队列,如果为false,那么消息就会被丢弃
      */
 //            channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
     /*
     拒绝应答
     参数 1:当前的消息标签
     参数 2:是否将当前消息放回队列,如果为false,那么消息就会被丢弃
      */
 //            channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
 }, s -> {
 });

basicConsume方法参数如下:

  • queue:消息队列名称,直接指定。

  • autoAck:自动应答。

    消费者从消息队列取出数据后,需要跟服务器进行确认应答,当服务器收到确认后,会自动将消息删除,如果开启自动应答,那么消息发出后会直接删除。

  • deliver:消息接收后的函数回调。

    可以在回调中对消息进行处理,处理完成后,需要给服务器确认应答。

  • cancel:当消费者取消订阅时进行的函数回调,这里暂时用不到。
    执行上述代码后,控制台输出:Hello World!,说明消息消费成功。

直连模式-Spring Boot操作

Spring Boot操作

Spring Boot集成RabbitMQ是现在主流的操作RabbitMQ的方式。

官方文档:https://docs.spring.io/spring-amqp/docs/current/reference/html/

1.引入依赖。

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

2.添加配置。 

 spring:
   rabbitmq:
     addresses: 127.0.0.1
     username: admin
     password: admin
     virtual-host: /test

3.配置类。

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.core.QueueBuilder;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  */
 @Configuration
 public class RabbitMqConfig {
     /**
      * 定义交换机,可以很多个
      * @return 交换机对象
      */
     @Bean("directExchange")
     public Exchange exchange(){
         return ExchangeBuilder.directExchange("amq.direct").build();
     }
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean("testQueue")
     public Queue queue(){
         return QueueBuilder
                 // 非持久化类型
                 .nonDurable("test_springboot")
                 .build();
     }
     /**
      * 定义绑定关系
      * @return 绑定关系
      */
     @Bean
     public Binding binding(@Qualifier("directExchange") Exchange exchange,
                            @Qualifier("testQueue") Queue queue){
         // 将定义的交换机和队列进行绑定
         return BindingBuilder
                 // 绑定队列
                 .bind(queue)
                 // 到交换机
                 .to(exchange)
                 // 使用自定义的routingKey
                 .with("test_springboot_key")
                 // 不设置参数
                 .noargs();
     }
 }

普通消费 

1.实现生产者。

 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer() {
         /*
         发送消息
         参数 1:指定交换机。
         参数 2:指定路由标识。
         参数 3:消息内容。
          */
         rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", "Hello World!");
     }
 }

运行代码后,查看可视化界面,可以看到创建了一个新的队列:

绑定关系也已经建立:

2.实现消费者。

消费者实际上就是一直等待消息然后进行处理的角色,这里只需要创建一个监听器就行了,它会一直等待消息到来然后再进行处理:

 import org.springframework.amqp.core.Message;
 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 直连队列监听器
  * @author CodeSail
  */
 @Component
 public class DirectListener {
     /**
      * 定义此方法为队列test_springboot的监听器,一旦监听到新的消息,就会接受并处理
      * @param message 消息内容
      */
     @RabbitListener(queues = "test_springboot")
     public void customer(Message message){
         System.out.println(new String(message.getBody()));
     }
 }

 3.启动服务。

可以看到,成功消费了消息。

消费并反馈

如果需要确保消息能够被消费者接受并处理,然后得到消费者的反馈,也是可以的。

1.定义生产者。

 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer() {
         // 会等待消费者消费然后返回响应结果
         Object res = rabbitTemplate.convertSendAndReceive("amq.direct", "test_springboot_key", "Hello World!");
         System.out.println("收到消费者响应:" + res);
     }
 }

2.定义生产者。 

 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 直连队列监听器
  * @author CodeSail
  */
 @Component
 public class DirectListener {
     /**
      * 定义此方法为队列test_springboot的监听器,一旦监听到新的消息,就会接受并处理
      * @param message 消息内容
      */
     @RabbitListener(queues = "test_springboot")
     public String customer(String message){
         System.out.println("1号消息队列监听器:" + message);
         return "收到!";
     }
 }

3.启动生产者发送消息。

可以看到,消费完成后接收到了反馈消息。

Json消息

 1.引入依赖。

 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-databind</artifactId>
     <version>2.14.2</version>
 </dependency>

2.定义对象。

 import lombok.Data;
 /**
  * 用户
  */
 @Data
 public class User {
     /**
      * 姓名
      */
     private String name;
     /**
      * 年龄
      */
     private Integer age;
 }

 3.定义Bean。

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.core.QueueBuilder;
 import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  */
 @Configuration
 public class RabbitMqConfig {
     ...
     /**
      * 构建Json转换器
      * @return Json转换器
      */
     @Bean
     public Jackson2JsonMessageConverter jackson2JsonMessageConverter(){
         return new Jackson2JsonMessageConverter();
     }
 }

4.定义消费者。

 import cn.codesail.rabbitmq.entity.User;
 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 直连队列监听器
  */
 @Component
 public class DirectListener {
     /**
      * 指定messageConverter为创建的Bean名称
      * @param user 用户
      */
     @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
     public void receiver(User user) {
         System.out.println(user);
     }
 }

5.定义生产者。

 import cn.codesail.rabbitmq.entity.User;
 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer() {
         // 发送Json消息
         User user = new User();
         user.setName("张三");
         user.setAge(18);
         rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
     }
 }

6.启动生产者发送消息。 

  1. 可以看到,对象转成了Json,消费者接收到Json转为的对象。

Spring Boot操作RabbitMQ是十分方便的,也是现在的主流,后续都用这种方式演示。


  • 环境
    • JDK 17.0.6
    • Maven 3.6.3
    • SpringBoot 3.0.4
    • spring-boot-starter-amqp 3.0.4
    • jackson-databind 2.14.2

工作队列模式

工作队列模式

工作队列模式结构图:

这种模式非常适合多个工人等待任务到来的场景。任务有多个,一个一个丢进消息队列,工人也有很多个,就可以将这些任务分配个各个工人,让他们各自负责一些任务,并且做的快的工人还可以多完成一些(能者多劳)。

要实现这种模式,只需要创建多个监听器即可。

先监听再发消息

这里先介绍先监听再发送消息的情况。

1.定义配置类

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.core.QueueBuilder;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  * 
  * @author CodeSail
  */
 @Configuration
 public class RabbitMqConfig {
     /**
      * 定义交换机,可以很多个
      * @return 交换机对象
      */
     @Bean("directExchange")
     public Exchange exchange(){
         return ExchangeBuilder.directExchange("amq.direct").build();
     }
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean("testQueue")
     public Queue queue(){
         return QueueBuilder
                 // 非持久化类型
                 .nonDurable("test_springboot")
                 .build();
     }
     /**
      * 定义绑定关系
      * @return 绑定关系
      */
     @Bean
     public Binding binding(@Qualifier("directExchange") Exchange exchange,
                            @Qualifier("testQueue") Queue queue){
         // 将定义的交换机和队列进行绑定
         return BindingBuilder
                 // 绑定队列
                 .bind(queue)
                 // 到交换机
                 .to(exchange)
                 // 使用自定义的routingKey
                 .with("test_springboot_key")
                 // 不设置参数
                 .noargs();
     }
 }

2.创建两个监听器,监听同一队列。 

 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 工作队列监听器
  *
  * @author 廖航
  */
 @Component
 public class WorkListener {
     @RabbitListener(queues = "test_springboot")
     public void receiver1(String message) {
         System.out.println("1号监听器:" + message);
     }
     @RabbitListener(queues = "test_springboot")
     public void receiver2(String message) {
         System.out.println("2号监听器:" + message);
     }
 }

 3.定义生产者。

 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import java.util.concurrent.TimeUnit;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer() throws InterruptedException {
         for (int i = 0; i < 10; i++) {
             TimeUnit.SECONDS.sleep(1);
             rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", "Hello World");
         }
     }
 }

 4.启动生产者发送消息。

可以看到,当同一个队列有多个监听器时,默认采用轮询的方式消费消息。

先发消息再监听

前面介绍了先监听再发消息的情况,下面介绍先发消息再监听的情况。

  1. 向队列中发送10条消息。

  2. 启动服务消费消息。

    可以看到,如果是一开始就存在消息,会被一个消费者一次性全部消费,这是因为没有对消费者的Prefetch count(预获取数量,一次性获取消息的最大数量)进行限制。

    默认的Prefetch count为250。

    也就是说如果希望消费者一次只拿一个消息,而不是将所有的消息全部都获取,需要设置Prefetch count。

要对这个数量进行配置,需要在配置类中定义一个自定义的ListenerContainerFactory,可以在这里设置消费者Channel的PrefetchCount的大小。

1.配置类中定义ListenerContainerFactory

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.core.QueueBuilder;
 import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
 import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  * 
  * @author CodeSail
  */
 @Configuration
 public class RabbitMqConfig {
     @Autowired
     private CachingConnectionFactory connectionFactory;
     ...
     @Bean(name = "listenerContainer")
     public SimpleRabbitListenerContainerFactory listenerContainer(){
         SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
         factory.setConnectionFactory(connectionFactory);
         // 将PrefetchCount设定为1表示一次只能取一个
         factory.setPrefetchCount(1);   
         return factory;
     }
 }

2.监听器中指定。 

 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 工作队列监听器
  *
  * @author 廖航
  */
 @Component
 public class WorkListener {
     @RabbitListener(queues = "test_springboot", containerFactory = "listenerContainer")
     public void receiver1(String message) {
         System.out.println("1号监听器:" + message);
     }
     @RabbitListener(queues = "test_springboot", containerFactory = "listenerContainer")
     public void receiver2(String message) {
         System.out.println("2号监听器:" + message);
     }
 }

 3.向队列中发送10条消息。

4.启动服务消费消息。 

可以看到,两个监听器又实现了轮询消费消息。

Prefetch count设为了1。

至此,工作队列模式的两种情况就介绍完毕了。


  • 环境
    • JDK 17.0.6
    • Maven 3.6.3
    • SpringBoot 3.0.4
    • spring-boot-starter-amqp 3.0.4

发布订阅模式

发布订阅模式结构图:

比如信用卡还款日临近了,那么就会给手机、邮箱发送消息,提示需要去还款了,但是手机短信和邮件发送并不一定是同一个业务提供的,但是现在又希望能够都去执行,就可以用到发布订阅模式,简而言之就是,发布一次,消费多个

实现这种模式需要用到另一种类型的交换机,叫做fanout(扇出)类型,这是一种广播类型,消息会被广播到所有与此交换机绑定的消息队列中。

这里使用默认的扇出交换机:

1.定义配置类。

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  */
 @Configuration
 public class RabbitMqConfig {
     /**
      * 定义交换机,可以很多个
      * @return 交换机对象
      */
     @Bean
     public Exchange fanoutExchange(){
         return ExchangeBuilder.fanoutExchange("amq.fanout").build();
     }
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean
     public Queue fanoutQueue1(){
         return new Queue("fanoutQueue1");
     }
     /**
      * 定义绑定关系
      * @return 绑定关系
      */
     @Bean
     public Binding binding1(@Qualifier("fanoutExchange") Exchange exchange,
                             @Qualifier("fanoutQueue1") Queue queue){
         // 将定义的交换机和队列进行绑定
         return BindingBuilder
                 // 绑定队列
                 .bind(queue)
                 // 到交换机
                 .to(exchange)
                 // 使用自定义的routingKey
                 .with("")
                 // 不设置参数
                 .noargs();
     }
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean
     public Queue fanoutQueue2(){
         return new Queue("fanoutQueue2");
     }
     /**
      * 定义绑定关系
      * @return 绑定关系
      */
     @Bean
     public Binding binding(@Qualifier("fanoutExchange") Exchange exchange,
                            @Qualifier("fanoutQueue2") Queue queue){
         // 将定义的交换机和队列进行绑定
         return BindingBuilder
                 // 绑定队列
                 .bind(queue)
                 // 到交换机
                 .to(exchange)
                 // 使用自定义的routingKey
                 .with("")
                 // 不设置参数
                 .noargs();
     }
 }

2.定义消费者。 

 import org.springframework.amqp.rabbit.annotation.Queue;
 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 发布订阅监听器
  */
 @Component
 public class FanoutListener {
     @RabbitListener(queuesToDeclare = {@Queue("fanoutQueue1")})
     public void receiver1(String message) {
         System.out.println("1号监听器:" + message);
     }
     @RabbitListener(queuesToDeclare = {@Queue("fanoutQueue2")})
     public void receiver2(String message) {
         System.out.println("2号监听器:" + message);
     }
 }

为了避免监听时没有该队列而报错,可以采用queuesToDeclare = {@Queue("队列名称")}的形式,这样如果没有该队列会自动创建该队列。 

3. 定义生产者。

 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import java.util.concurrent.TimeUnit;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer()  {
         rabbitTemplate.convertAndSend("amq.fanout", "", "Hello World");
     }
 }

4.启动生产者,发送消息。

可以看到,发送一条消息,两个消费者都收到了消息,这就是发布订阅模式。


  • 环境
    • JDK 17.0.6
    • Maven 3.6.3
    • SpringBoot 3.0.4
    • spring-boot-starter-amqp 3.0.4

路由模式

路由模式结构图:

1.定义配置类

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  */
 @Configuration
 public class RabbitMqConfig {
     /**
      * 定义交换机,可以很多个
      * @return 交换机对象
      */
     @Bean
     public Exchange directExchange(){
         return ExchangeBuilder.directExchange("amq.direct").build();
     }
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean
     public Queue directQueue(){
         return new Queue("directQueue");
     }
     /**
      * 定义绑定关系
      * @return 绑定关系
      */
     @Bean
     public Binding binding1(@Qualifier("directExchange") Exchange exchange,
                             @Qualifier("directQueue") Queue queue){
         // 将定义的交换机和队列进行绑定
         return BindingBuilder
                 // 绑定队列
                 .bind(queue)
                 // 到交换机
                 .to(exchange)
                 // 使用自定义的routingKey
                 .with("test_springboot_key1")
                 // 不设置参数
                 .noargs();
     }
     /**
      * 定义绑定关系
      * @return 绑定关系
      */
     @Bean
     public Binding binding(@Qualifier("directExchange") Exchange exchange,
                            @Qualifier("directQueue") Queue queue){
         // 将定义的交换机和队列进行绑定
         return BindingBuilder
                 // 绑定队列
                 .bind(queue)
                 // 到交换机
                 .to(exchange)
                 // 使用自定义的routingKey
                 .with("test_springboot_key2")
                 // 不设置参数
                 .noargs();
     }
 }

2.定义消费者。

 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 直连队列监听器
  */
 @Component
 public class DirectListener {
     /**
      * 监听直连队列消息
      */
     @RabbitListener(queues = "directQueue")
     public void receiver(String message) {
         System.out.println("直连队列接收到消息:" + message);
     }
 }

3.定义生产者。 

 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer()  {
         rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key1", "Hello World");
         rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key2", "Hello World");
     }
 }

 4.启动生产者,发送消息

可以看到,不同的routingKey都发送到了目标队列进行消费,这就是路由模式。


  • 环境
    • JDK 17.0.6
    • Maven 3.6.3
    • SpringBoot 3.0.4
    • spring-boot-starter-amqp 3.0.4

主题模式

主题模式结构图:

主题模式实际上就是一种模糊匹配的模式,可以将routingKey以模糊匹配的方式去进行转发。

可以使用*#来表示:

  • *:任意的一个单词。
  • #:0个或多个单词。

1.定义配置类。

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  */
 @Configuration
 public class RabbitMqConfig {
     /**
      * 定义交换机,可以很多个
      * @return 交换机对象
      */
     @Bean
     public Exchange topicExchange(){
         return ExchangeBuilder.topicExchange("amq.topic").build();
     }
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean
     public Queue topicQueue(){
         return new Queue("topicQueue");
     }
     /**
      * 定义绑定关系
      * @return 绑定关系
      */
     @Bean
     public Binding binding(@Qualifier("topicExchange") Exchange exchange,
                            @Qualifier("topicQueue") Queue queue){
         // 将定义的交换机和队列进行绑定
         return BindingBuilder
                 // 绑定队列
                 .bind(queue)
                 // 到交换机
                 .to(exchange)
                 // 使用自定义的routingKey
                 .with("*.test.#")
                 // 不设置参数
                 .noargs();
     }
 }

2. 定义消费者。

 import org.springframework.amqp.rabbit.annotation.Queue;
 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 主题队列监听器
  */
 @Component
 public class TopicListener {
     /**
      * 监听主题队列消息
      */
     @RabbitListener(queuesToDeclare = {@Queue("topicQueue")})
     public void receiver(String message) {
         System.out.println("主题队列接收到消息:" + message);
     }
 }

3.定义生产者。

 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer()  {
         rabbitTemplate.convertAndSend("amq.topic", "test.1", "Hello World 1");
         rabbitTemplate.convertAndSend("amq.topic", "1.test", "Hello World 2");
         rabbitTemplate.convertAndSend("amq.topic", "1.test.1", "Hello World 3");
     }
 }

 4.启动服务。

此时队列已创建。

绑定关系已设置。

启动生产者,发送消息。

可以看到,第一条消息的routingKey(test.1)由于不匹配*.test.#而没有被队列接收到。

除了这里使用的默认主题交换机之外,还有一个叫做amq.rabbitmq.trace的交换机:

这是用于帮助记录和追踪生产者和消费者使用消息队列的交换机,它是一个内部的交换机,这里就不演示了。


  • 环境
    • JDK 17.0.6
    • Maven 3.6.3
    • SpringBoot 3.0.4
    • spring-boot-starter-amqp 3.0.4

头部模式

头部模式是根据头部信息来决定的,在发送的消息中是可以携带一些头部信息的(类似于HTTP),可以根据这些头部信息来决定路由到哪一个消息队列中。

1.定义配置类。

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.HeadersExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * RabbitMQ配置类
 */
@Configuration
public class RabbitMqConfig {
    /**
     * 定义交换机,可以很多个
     * @return 交换机对象
     */
    @Bean
    public HeadersExchange headersExchange(){
        return ExchangeBuilder.headersExchange("amq.headers").build();
    }
    /**
     * 定义消息队列
     * @return 消息队列对象
     */
    @Bean
    public Queue headersQueue(){
        return new Queue("headersQueue");
    }
    /**
     * 定义绑定关系
     * @return 绑定关系
     */
    @Bean
    public Binding binding(@Qualifier("headersExchange") HeadersExchange exchange,
                           @Qualifier("headersQueue") Queue queue){
        // 将定义的交换机和队列进行绑定
        return BindingBuilder
                // 绑定队列
                .bind(queue)
                // 到交换机
                .to(exchange)
//                .whereAny("a", "b").exist();                              // 这个是只要存在任意一个指定的头部Key就行
//                .whereAny(Collections.singletonMap("a", "b")).match();    // 传入Map也行,批量指定键值对
//                .whereAll("a", "b").exist();                              // 这个是必须存在所有指定的的头部Key
//                .whereAll(Collections.singletonMap("a", "b")).match();    // 传入Map也行,批量指定键值对
                .where("test").matches("hello");                  // 比如我们现在需要消息的头部信息中包含test,并且值为hello才能转发给我们的消息队列
    }
}

2.定义消费者。

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
 * 头部监听器
 */
@Component
public class HeadersListener {
    /**
     * 监听头部队列消息
     */
    @RabbitListener(queuesToDeclare = {@Queue("headersQueue")})
    public void receiver(String message) {
        System.out.println("头部队列接收到消息:" + message);
    }
}

3.定义生产者。

import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitMqSpringBootTests {
    /**
     * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
     */
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 生产者
     */
    @Test
    void producer()  {
        Message message = MessageBuilder.withBody("Hello World".getBytes()).setHeader("test","hello").build();
        rabbitTemplate.convertAndSend("amq.headers", "", message);
    }
}

4.启动生产者发送消息。

可以看到,通过头部的匹配,队列成功接收到了消息。


  • 环境
    • JDK 17.0.6
    • Maven 3.6.3
    • SpringBoot 3.0.4
    • spring-boot-starter-amqp 3.0.4

死信队列

概述

消息队列中的数据,如果迟迟没有消费者来处理,就会一直占用消息队列的空间。

比如抢车票的场景,用户下单高铁票之后,会进行抢座,然后再进行付款,但是如果用户下单之后并没有及时的付款,这张票不可能一直让这个用户占用着,因为这样别人就买不到这张票了,所以会在一段时间后超时,让这张票可以继续被其他人购买。

这时,就可以使用死信队列,将那些用户超时未付款的或是用户主动取消的订单,进行进一步的处理。

那么如何构建这样的一种使用模式呢?实际上本质就是一个死信交换机+死信队列

当正常队列中的消息被判定为死信时,会被发送到对应的死信交换机,然后再通过交换机发送到死信队列中,死信队列也有对应的消费者去处理消息。

判定为死信一般是3种情况:

  • 消息被拒绝(basic.reject / basic.nack),并且requeue = false
  • 消息超时未消费。
  • 消息队列达到最大长度。

产生死信

消息被拒绝

1.在配置类中创建一个新的死信交换机和死信队列,并进行绑定。

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.core.QueueBuilder;
 import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  */
 @Configuration
 public class RabbitMqConfig {
     ...
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean("testQueue")
     public Queue queue(){
         return QueueBuilder
                 // 非持久化类型
                 .nonDurable("test_springboot")
                 // 指定死信交换机
                 .deadLetterExchange("dl.direct")
                 // 指定死信RoutingKey
                 .deadLetterRoutingKey("dl_test_springboot_key")   
                 .build();
     }
     /**
      * 构建死信交换机
      * @return 死信交换机
      */
     @Bean
     public Exchange dlExchange(){
         // 创建一个新的死信交换机
         return ExchangeBuilder.directExchange("dl.direct").build();
     }
     /**
      * 构建死信队列
      * @return 死信队列
      */
     @Bean
     public Queue dlQueue(){
         return QueueBuilder
                 .nonDurable("dl_test_springboot")
                 .build();
     }
     /**
      * 死信交换机和死信队列绑定
      * @param exchange 死信交换机
      * @param queue 死信队列
      * @return 绑定对象
      */
     @Bean
     public Binding dlBinding(@Qualifier("dlExchange") Exchange exchange,
                              @Qualifier("dlQueue") Queue queue){
         return BindingBuilder
                 .bind(queue)
                 .to(exchange)
                 .with("dl_test_springboot_key")
                 .noargs();
     }
     ...
 }

2.监听正常队列和死信队列消息。

 import cn.codesail.rabbitmq.entity.User;
 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;
 /**
  * 直连队列监听器
  */
 @Component
 public class DirectListener {
     /**
      * 监听正常队列消息
      */
     @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
     public void receiver(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, User user) throws Exception {
         // 拒绝消息。第二个参数为true则消息返回队列,第二个参数为false则消息不返回队列,成为死信
         channel.basicReject(deliveryTag, false);
         System.out.println("正常队列接收到消息:" + user);
     }
     /**
      * 监听死信队列消息
      */
     @RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
     public void receiverDl(User user) {
         System.out.println("死信队列接收到消息:" + user);
     }
 }

正常队列消息的监听种拒绝了消息,且不返回队列,成为了死信,就会被死信队列的监听接收到。

3.删除原队列。删除了原队列才能创建与死信队列绑定的队列。

4.实现生产者。

 import cn.codesail.rabbitmq.entity.User;
 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer() {
         // 发送Json消息
         User user = new User();
         user.setName("张三");
         user.setAge(18);
         rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
     }
 }

5.启动生产者发送消息: 

可以看到,死信队列接收到了消息。

消息超时未消费

1.设定队列TTL值。

 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.core.QueueBuilder;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  * 
  * @author CodeSail
  */
 @Configuration
 public class RabbitMqConfig {
     ...
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean("testQueue")
     public Queue queue(){
         return QueueBuilder
                 // 非持久化类型
                 .nonDurable("test_springboot")
                 // 指定死信交换机
                 .deadLetterExchange("dl.direct")
                 // 指定死信RoutingKey
                 .deadLetterRoutingKey("dl_test_springboot_key")
                 // 如果5秒没处理,就自动删除
                 .ttl(5000)
                 .build();
     }
     ...
 }

2.取消正常队列监听。

 import cn.codesail.rabbitmq.entity.User;
 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;
 /**
  * 直连队列监听器
  */
 @Component
 public class DirectListener {
     /**
      * 监听正常队列消息
      */
 //    @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
 //    public void receiver(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, User user) throws Exception {
 //        // 拒绝消息。第二个参数为true则消息返回队列,第二个参数为false则消息不返回队列,成为死信
 //        channel.basicReject(deliveryTag, false);
 //        System.out.println("正常队列接收到消息:" + user);
 //    }
     /**
      * 监听死信队列消息
      */
     @RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
     public void receiverDl(User user) {
         System.out.println("死信队列接收到消息:" + user);
     }
 }

3.实现生产者。

4.删除原队列。删除了原队列才能创建与死信队列绑定的设定了TTL的队列。

5.启动服务,监听消息。

6.启动生产者发送消息,等待5秒:

可以看到,死信队列接收到了消息。

消息队列达到最大长度

1.设置队列最大消息长度。

 import org.springframework.amqp.core.Binding;
 import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.ExchangeBuilder;
 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.core.QueueBuilder;
 import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
  * RabbitMQ配置类
  */
 @Configuration
 public class RabbitMqConfig {
     ...
     /**
      * 定义消息队列
      * @return 消息队列对象
      */
     @Bean("testQueue")
     public Queue queue(){
         return QueueBuilder
                 // 非持久化类型
                 .nonDurable("test_springboot")
                 // 指定死信交换机
                 .deadLetterExchange("dl.direct")
                 // 指定死信RoutingKey
                 .deadLetterRoutingKey("dl_test_springboot_key")
                 // 最大长度设定为3
                 .maxLength(3)
                 .build();
     }
     ...
 }

 2.取消正常队列监听。

 import cn.codesail.rabbitmq.entity.User;
 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
  * 直连队列监听器
  */
 @Component
 public class DirectListener {
     /**
      * 监听正常队列消息
      */
 //    @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
 //    public void receiver(User user) {
 //        System.out.println("正常队列接收到消息:" + user);
 //    }
     /**
      * 监听死信队列消息
      */
     @RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
     public void receiverDl(User user) {
         System.out.println("死信队列接收到消息:" + user);
     }
 }

3.删除原队列。删除了原队列才能创建与死信队列绑定的设定了最大长度的队列。

4.定义生产者。

 import cn.codesail.rabbitmq.entity.User;
 import org.junit.jupiter.api.Test;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import java.util.concurrent.TimeUnit;
 @SpringBootTest
 class RabbitMqSpringBootTests {
     /**
      * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
      */
     @Autowired
     private RabbitTemplate rabbitTemplate;
     /**
      * 生产者
      */
     @Test
     void producer() throws InterruptedException {
         for (int i = 0; i < 4; i++) {
             User user = new User();
             user.setName("张三" + i);
             user.setAge(18);
             rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
         }
     }
 }

5.启动生产者发送消息。 

可以看到,队列的第一个元素被挤出成为了死信。

队列就类似于一个管道,当管道的人占满了,最后进去的人就会把最前面的人挤出去。


  • 环境
    • JDK 17.0.6
    • Maven 3.6.3
    • SpringBoot 3.0.4
    • spring-boot-starter-amqp 3.0.4
    • jackson-databind 2.14.2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值