RabbitMQ学习文档(入门篇(Demo使用Spring编写))

目录

一、Rabbitmq说明

1、官网

首页: https://www.rabbitmq.com
示例: https://www.rabbitmq.com/getstarted.html

2、下载地址

https://www.rabbitmq.com/download.html

3、概念

  1. RabbitMQ是一个部署最广泛的开源消息中间件(消息代理)——来自官网首页
  2. 无论在本地还是在云端,RabbitMQ是一个轻量级、容易部署的,支持多种消息协议,可以部署在分布式系统之中,满足大规模、高可用的要求
  3. 异步消息:支持多种消息协议,消息队列、消息确认、灵活的队列路由、多种消息类型
  4. 开发者体验:支持多种部署方式,比如Docker等;拥有多种语言客户端,不受开发语言的限制,可以进行跨语言系统之间的消息传递
  5. 分布式部署:可以部署RabbitMQ集群来获取高可用性、高吞吐量的优势
  6. 认证授权:支持身份认证,权限授予
  7. 管理监控:可以使用命令行或者UI界面来管理或者监控RabbitMQ
  8. 支持多协议:RabbitMQ支持多种协议,其中AMQP(Advanced Message Queuing Protocol:即高级消息队列协议)只是其中一种,AMQP它是一个开放的、通用的消息传递协议。

4、RabbitMQ比喻、行话

4.1、RabbitMQ的类比

RabbitMQ是一个消息中间件,用来接收和转发消息。我们来类比一下,你可以把它想象成一个邮局,当你把一封邮件放在一个邮箱之中,你确信这封邮件最终能发送到收件人手中。在这个类比中,RabbitMQ是一个邮箱、一个邮局、一个快递员

4.2、RabbitMQ中的行话

  • 生产者:生产意味着发送,发送消息的程序就是生产者
  • 队列:队列相当于类比中的邮箱,消息在RabbitMQ和应用程序之间流通,不过中间存储在队列中,队列相当于一个消息缓冲区
  • 消费者:消费者是一个等待接收消息的程序

三者关系:生产者、队列、消费者不用在同一台主机上,当然也可以在同一台主机上

二、模式解读

1、说明

1.1、快速开始官方Demo

https://www.rabbitmq.com/getstarted.html

1.2、SpringBoot版本

以下Demo案例对应RabbitMQ学习文档(入门篇(Demo使用SpringBoot编写))中的Demo代码

2、Hello World!(简单队列)

2.1、画图说明

在这里插入图片描述

注意:P是生产者,中间红色矩形是队列,C是消费者

2.2、概念分析和使用场景

概念分析:

最简单的消息发送接收模式;一个生产者,一个消费者来进行消息产生和消费

使用场景:

用户在使用我们系统时将产生多种用户行为(浏览、点赞、收藏、转发、评论),然后有一家公司需要对用户的行为进行分析,这种模式就适合这种情况

2.3、示例代码

2.3.1、pom依赖

<!--version仅供参考,依需设置-->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.14.2</version>
</dependency>

官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找

2.3.2、生产者代码

public class Provider {
    
    
    // 队列名称
    private static final String QUEUE_NAME = "hello_wold";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        /**
         * 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
         * 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
         */
        try (
                // 根据上面设置的参数来创建连接
                Connection connection = factory.newConnection();
                // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
                Channel channel = connection.createChannel();
        ) {
    
    
            /**
             * 创建队列
             * 说明:一保证消息发送之前,队列已经存在了
             * 参数1:队列名称
             * 参数2:是否持久化
             *          队列持久化之后,即使RabbitMQ重启,队列依然存在
             * 参数3:是否排他
             *          设置排他之后,仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
             * 参数4:附加参数
             *          比如可以设置死信队列等
             */
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            // 消息内容
            String message = "Hello World!";
            /**
             * 发送消息到队列
             * 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
             * 参数1:交换机名称
             *          ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
             * 参数2:路由名称
             *          由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
             * 参数3:其他消息属性
             *          MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
             * 参数4,消息内容
             *          消息内容是字节数组,所以我们需要使用getBytes()方法
             */
            channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

2.3.2、消费者代码

public class Consumer {
    
    

    private static final String QUEUE_NAME = "hello";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            /**
             * 创建队列
             * 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
             * 参数1:队列名称
             * 参数2:是否持久化
             *          队列持久化之后,即使RabbitMQ重启,队列依然存在
             * 参数3:是否排他
             *          设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
             * 参数4:附加参数
             *          比如可以设置死信队列等
             */
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

3、Work queues(工作队列 / 任务队列)

3.1、画图说明

在这里插入图片描述
注意:P是生产者,中间红色矩形是队列,C是消费者

3.2、概念分析和使用场景

概念分析:

采用消费者竞争模式,用于耗时任务,符合能者多劳原则,在多个消费者之间分配任务,常用于执行资源密集型任务,也就是说消费比较多,消费比较耗时的情况

使用场景:

订单支付之后,需要物流系统进行处理,由于消息比较多,并且处理起来比较复杂,因此单个物流系统处理处理效率太低,所以我们需要使用多个服务器中的物流系统进行消息处理,这种情况就很适合使用

3.3、示例代码

3.3.1、pom依赖

<!--version仅供参考,依需设置-->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.14.2</version>
</dependency>

官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找

3.3.2、生产者代码

public class Provider {
    
    
    // 队列名称
    private static final String QUEUE_NAME = "work_queues";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        /**
         * 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
         * 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
         */
        try (
                // 根据上面设置的参数来创建连接
                Connection connection = factory.newConnection();
                // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
                Channel channel = connection.createChannel();
        ) {
    
    
            /**
             * 创建队列
             * 说明:一保证消息发送之前,队列已经存在了
             * 参数1:队列名称
             * 参数2:是否持久化
             *          队列持久化之后,即使RabbitMQ重启,队列依然存在
             * 参数3:是否排他
             *          设置排他之后,仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
             * 参数4:附加参数
             *          比如可以设置死信队列等
             */
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            // 发送多个消息
            for (int i = 0; i < 10; i++) {
    
    
                // 消息内容
                String message = String.format("Hello,我是%s号消息", i);
                /**
                 * 发送消息到队列
                 * 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
                 * 参数1:交换机名称
                 *          ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
                 * 参数2:路由名称
                 *          由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
                 * 参数3:其他消息属性
                 *          MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
                 * 参数4,消息内容
                 *          消息内容是字节数组,所以我们需要使用getBytes()方法
                 */
                channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

3.3.3、消费者1代码

public class Consumer1 {
    
    

    private static final String QUEUE_NAME = "work_queues";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 通道中只能存在一条未确认的消息,在消费者进行消息确认之前,不要给消费者发送新消息,符合能者多劳思想
            channel.basicQos(1);
            /**
             * 创建队列
             * 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
             * 参数1:队列名称
             * 参数2:是否持久化
             *          队列持久化之后,即使RabbitMQ重启,队列依然存在
             * 参数3:是否排他
             *          设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
             * 参数4:附加参数
             *          比如可以设置死信队列等
             */
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                // 模拟消息处理过程
                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println("Consumer1接收到消息,消息内容:" + str);
                /**
                 * 手动确认消息
                 * 参数1:用来确认消息的发送标签
                 * 参数2:是否确认当前通道中的所有消息
                 *          false(建议):只确认当前标签对应的消息
                 *          true:确认当前通道中的所有未确认消息,这样可能造成部分正在被消费者处理的消息被确认,如果这些消息处理失败了,那么这些消息就丢失了
                 */
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

3.3.4、消费者1代码

public class Consumer2 {
    
    

    private static final String QUEUE_NAME = "work_queues";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 通道中只能存在一条未确认的消息,在消费者进行消息确认之前,不要给消费者发送新消息,符合能者多劳思想
            channel.basicQos(1);
            /**
             * 创建队列
             * 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
             * 参数1:队列名称
             * 参数2:是否持久化
             *          队列持久化之后,即使RabbitMQ重启,队列依然存在
             * 参数3:是否排他
             *          设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
             * 参数4:附加参数
             *          比如可以设置死信队列等
             */
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                // 模拟消息处理过程
                try {
    
    
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println("Consumer2接收到消息,消息内容:" + str);
                /**
                 * 手动确认消息
                 * 参数1:用来确认消息的发送标签
                 * 参数2:是否确认当前通道中的所有消息
                 *          false(建议):只确认当前标签对应的消息
                 *          true:确认当前通道中的所有未确认消息,这样可能造成部分正在被消费者处理的消息被确认,如果这些消息处理失败了,那么这些消息就丢失了
                 */
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

4、Publish/Subscribe(发布订阅模式)

4.1、画图说明

在这里插入图片描述

注意:P是生产者,X是交换机,中间红色矩形是队列,CN是消费者

4.2、概念分析和使用场景

概念分析:

交换机采用fanout扇出模式,可以将消息发送给所有与之相连的队列,并且和消息路由键、队列和交换机

使用场景:

用户在使用我们系统时将产生多种用户行为(浏览、点赞、收藏、转发、评论),然后有两家公司都需要对用户的行为进行分析,这种模式就适合这种情况

4.3、示例代码

4.3.1、pom依赖

<!--version仅供参考,依需设置-->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.14.2</version>
</dependency>

官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找

4.3.2、生产者代码

public class Provider {
    
    
    // 交换机名称
    private static final String EXCHANGE_NAME = "public_subscribe";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        /**
         * 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
         * 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
         */
        try (
                // 根据上面设置的参数来创建连接
                Connection connection = factory.newConnection();
                // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
                Channel channel = connection.createChannel();
        ) {
    
    
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;fanout代表扇出模式,这种模式可以将消息发送到和该交换机绑定的所有队列
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            // 消息内容
            String message = "Hello World!";
            /**
             * 发送消息到队列
             * 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
             * 参数1:交换机名称
             *          EXCHANGE_NAME是交换机名称
             * 参数2:路由名称
             *          当前交换机的类型是fanout,所以该交换机会将消息发送给所有连接到该交换机上的队列,因此路由将被忽略,所以写成空串即可
             * 参数3:其他消息属性,可以设置消息持久化,目前没有设置
             * 参数4,消息内容
             *          消息内容是字节数组,所以我们需要使用getBytes()方法
             */
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

4.3.3、消费者1代码

public class Consumer1 {
    
    

    // 交换机名称
    private static final String EXCHANGE_NAME = "public_subscribe";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;fanout代表扇出模式,这种模式可以将消息发送到和该交换机绑定的所有队列
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            /**
             * 创建临时队列
             * 作用: 创建一个具有非持久、独占、自动删除的队列
             * 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
             *          所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
             *          正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
             *          一旦我们断开消费者的连接,队列就会被自动删除
             */
            String queueName = channel.queueDeclare().getQueue();
            /**
             * 将交换机和队列绑定
             * 参数1:队列名称
             * 参数2:交换机名称
             * 参数3:交换机和队列绑定的路由,由于本次交换机使用fanout扇出模式,只要我们和交换机绑定之后,交换机接收到的所有消息都会发送给队列,和路由键无关
             */
            channel.queueBind(queueName, EXCHANGE_NAME, "");
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

4.3.4、消费者2代码

public class Consumer2 {
    
    

    // 交换机名称
    private static final String EXCHANGE_NAME = "public_subscribe";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;fanout代表扇出模式,这种模式可以将消息发送到和该交换机绑定的所有队列
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            /**
             * 创建临时队列
             * 作用: 创建一个具有非持久、独占、自动删除的队列
             * 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
             *          所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
             *          正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
             *          一旦我们断开消费者的连接,队列就会被自动删除
             */
            String queueName = channel.queueDeclare().getQueue();
            /**
             * 将交换机和队列绑定
             * 参数1:队列名称
             * 参数2:交换机名称
             * 参数3:交换机和队列绑定的路由,由于本次交换机使用fanout扇出模式,只要我们和交换机绑定之后,交换机接收到的所有消息都会发送给队列,和路由键无关
             */
            channel.queueBind(queueName, EXCHANGE_NAME, "");
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

5、Routing(路由模式)

5.1、画图说明

在这里插入图片描述

注意:P是生产者,X是交换机,QN是队列,CN是消费者

5.2、概念分析和使用场景

概念分析:

交换机采用direct模式,可以根据消息路由键进行有选择性的接收消息。首先一条消息有一个路由键,然后交换机和队列也是通过路由键进行绑定,当两个路由键完全相等的时候,交换机就会把消息发送给这个队列,其中队列和交换机可以使用多个路由键进行绑定,所以可以通过消息路由键来控制消息将会发送到哪个队列

使用场景:

日志存在多种等级,不同的等级处理方式不同,其中error级别的消息不仅要存储到磁盘,还需要在控制台展示,而info、warn等级别的消息只需要在控制台展示就可以了,这就可以使用路由模式

5.3、示例代码

5.3.1、pom依赖

<!--version仅供参考,依需设置-->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.14.2</version>
</dependency>

官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找

5.3.2、生产者代码

public class Provider {
    
    
    // 交换机名称
    private static final String EXCHANGE_NAME = "routing";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        /**
         * 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
         * 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
         */
        try (
                // 根据上面设置的参数来创建连接
                Connection connection = factory.newConnection();
                // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
                Channel channel = connection.createChannel();
        ) {
    
    
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 消息1内容
            String message = "error message";
            /**
             * 发送消息到队列
             * 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
             * 参数1:交换机名称
             *          EXCHANGE_NAME是交换机名称
             * 参数2:路由名称
             * 参数3:其他消息属性,可以设置消息持久化,目前没有设置
             * 参数4,消息内容
             *          消息内容是字节数组,所以我们需要使用getBytes()方法
             */
            channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes(StandardCharsets.UTF_8));
            // 消息2内容
            message = "info message";
            channel.basicPublish(EXCHANGE_NAME, "info", null, message.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

5.3.3、消费者1代码

public class Consumer1 {
    
    

    // 交换机名称
    private static final String EXCHANGE_NAME = "routing";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            /**
             * 创建临时队列
             * 作用: 创建一个具有非持久、独占、自动删除的队列
             * 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
             *          所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
             *          正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
             *          一旦我们断开消费者的连接,队列就会被自动删除
             */
            String queueName = channel.queueDeclare().getQueue();
            /**
             * 将交换机和队列绑定,可以接受路由键为error或者info的消息
             * 参数1:队列名称
             * 参数2:交换机名称
             * 参数3:交换机和队列绑定的路由键
             */
            channel.queueBind(queueName, EXCHANGE_NAME, "error");
            channel.queueBind(queueName, EXCHANGE_NAME, "info");
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

5.3.4、消费者2代码

public class Consumer2 {
    
    

    // 交换机名称
    private static final String EXCHANGE_NAME = "routing";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            /**
             * 创建临时队列
             * 作用: 创建一个具有非持久、独占、自动删除的队列
             * 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
             *          所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
             *          正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
             *          一旦我们断开消费者的连接,队列就会被自动删除
             */
            String queueName = channel.queueDeclare().getQueue();
            /**
             * 将交换机和队列绑定,只能接受消息路由键为error的消息
             * 参数1:队列名称
             * 参数2:交换机名称
             * 参数3:交换机和队列绑定的路由键
             */
            channel.queueBind(queueName, EXCHANGE_NAME, "error");
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

6、Topics(话题模式)

6.1、画图说明

在这里插入图片描述

注意:P是生产者,X是交换机,QN是队列,CN是消费者

6.2、概念分析和使用场景

概念分析:

使用场景:

6.3、示例代码

6.3.1、pom依赖

<!--version仅供参考,依需设置-->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.14.2</version>
</dependency>

官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找

6.3.2、生产者代码

public class Provider {
    
    
    // 交换机名称
    private static final String EXCHANGE_NAME = "topics";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        /**
         * 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
         * 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
         */
        try (
                // 根据上面设置的参数来创建连接
                Connection connection = factory.newConnection();
                // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
                Channel channel = connection.createChannel();
        ) {
    
    
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;topic代表主题模式,这种模式代表队列和交换机绑定的路由键中可以包含特殊字符,比如*(代替一个单词)和#(代替零个或多个单词)
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            // 消息1内容
            String message = "快速的橘色兔子";
            /**
             * 发送消息到队列
             * 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
             * 参数1:交换机名称
             *          EXCHANGE_NAME是交换机名称
             * 参数2:路由名称,交换机为topic模式情况下要求路由键必须是单词列表,并且以.分隔
             * 参数3:其他消息属性,可以设置消息持久化,目前没有设置
             * 参数4,消息内容
             *          消息内容是字节数组,所以我们需要使用getBytes()方法
             */
            channel.basicPublish(EXCHANGE_NAME, "quick.orange.rabbit", null, message.getBytes(StandardCharsets.UTF_8));
            // 消息2内容
            message = "快速的褐色狐狸";
            channel.basicPublish(EXCHANGE_NAME, "quick.brown.fox", null, message.getBytes(StandardCharsets.UTF_8));
            // 消息3内容
            message = "慢吞吞的黑色熊猫";
            channel.basicPublish(EXCHANGE_NAME, "lazy.black.panda", null, message.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

6.3.3、消费者1代码

public class Consumer1 {
    
    

    // 交换机名称
    private static final String EXCHANGE_NAME = "topics";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;topic代表主题模式,这种模式代表队列和交换机绑定的路由键中可以包含特殊字符,比如*(代替一个单词)和#(代替零个或多个单词)
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            /**
             * 创建临时队列
             * 作用: 创建一个具有非持久、独占、自动删除的队列
             * 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
             *          所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
             *          正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
             *          一旦我们断开消费者的连接,队列就会被自动删除
             */
            String queueName = channel.queueDeclare().getQueue();
            /**
             * 将交换机和队列绑定,可以接受路由键为error或者info的消息
             * 参数1:队列名称
             * 参数2:交换机名称
             * 参数3:交换机和队列绑定的路由键
             */
            channel.queueBind(queueName, EXCHANGE_NAME, "quick.*.*");
            channel.queueBind(queueName, EXCHANGE_NAME, "lazy.#");
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

6.3.4、消费者2代码

public class Consumer2 {
    
    

    // 交换机名称
    private static final String EXCHANGE_NAME = "topics";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;topic代表主题模式,这种模式代表队列和交换机绑定的路由键中可以包含特殊字符,比如*(代替一个单词)和#(代替零个或多个单词)
             */
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            /**
             * 创建临时队列
             * 作用: 创建一个具有非持久、独占、自动删除的队列
             * 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
             *          所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
             *          正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
             *          一旦我们断开消费者的连接,队列就会被自动删除
             */
            String queueName = channel.queueDeclare().getQueue();
            /**
             * 将交换机和队列绑定,只能接受消息路由键为error的消息
             * 参数1:队列名称
             * 参数2:交换机名称
             * 参数3:交换机和队列绑定的路由键
             */
            channel.queueBind(queueName, EXCHANGE_NAME, "*.*.panda");
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

三、难点解读

1、try—with—resource语法为什么可以在生产者端使用,而不能在消费者端使用

生产者: try中代码执行完毕之后,自动关闭通道,避免在finally中写关闭通道的冗余代码;原因:Channel接口继承了AutoCloseable接口,所以try{}中代码执行完毕,channel.close()方法将会自动调用进行通道关闭

在这里插入图片描述

消费者: 消费者需要持续性异步监听通道中的消息,所以不能关闭通道,对应连接来说,连接可以复用,所以不用关闭,这对生产者也是一样的

在这里插入图片描述

2、连接和通道的关系

一个连接中可以有多个通道,通道用于消息发送或者接收,所以我们可以复用连接来节省资源

在这里插入图片描述

3、队列持久化的作用

队列持久化之后,即使RabbitMQ重启,队列依然存在,这仅仅是队列持久化,和消息持久化无关
在这里插入图片描述

4、队列不应该自动删除的原因

队列的创建和销毁都是需要耗费系统资源的,所以最好在创建之后就不在销毁,另外队列被删除之后,消费者也会报错,所以我们需要在创建队列的时候设置队列不自动删除,代码如下:
在这里插入图片描述
如果设置为false,那么在队列中没有消息的时候,队列将会被自动删除,这种情况我们是不允许出现的

5、为什么在生产者和消费者代码中都声明队列?

生产者: 消息发送之前队列一定要存在

生产者: 只有连接到对应队列才可以接收消息

建议: 生产者和消费者两处声明的队列代码一致,毕竟队列一旦创建结束就不会再次创建了,这样可以出现问题时不被该问题困扰

在这里插入图片描述

6、默认交换机会和所有队列建立默认连接吗?

在RabbitMQ控制台可以找到如下内容:

The default exchange is implicitly bound to every queue, with a routing key equal to the queue name. It is not possible to explicitly bind to, or unbind from the default exchange. It also cannot be deleted.

翻译过来的意思如下:

默认交换机隐式绑定到每个队列,路由密钥等于队列名称。无法显式绑定到默认exchange或从默认exchange解除绑定。它也不能被删除。

这句话的位置如下:

在这里插入图片描述
可以看出默认交换机的类型是direct

如果想把消息发送到默认交换机上,在生产者端用代码表示如下:

// 参数1:默认交换机名称
// 参数2::路由键名称,由于队列和默认交换机的绑定路由键是队列名称,所以我们发送本条消息的路由键就应该是队列名称,这样才能让消息从默认交换机到达队列
// 参数3:消息持久化参数
// 参数4:消息字节数组
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));

具体流程如下:
在这里插入图片描述
如果我们想把消息发送到默认交换机上,然后让默认交换机把消息发送给队列,那么发送消息的路由键一定要和队列绑定交换机时的路由键一致,其中队列和默认交换机绑定的路由键就是队列名称,所以我们需要在发送者端设置消息路由键是队列名称

7、生产者端如何进行消息持久化

消息持久化的前提是队列持久化,创建队列代码如下:
在这里插入图片描述
解释:只有队列是持久化的,那么当RabbitMQ重启的时候,队列才会存在,而消息和队列绑定,只有队列存在的前提下,消息才有存在的可能性

需要在发送消息的时候进行持久化,消息持久化配置如下:在这里插入图片描述

解释:这行代码是写在发送者端的,用来进行消息发送,消息发送到默认交换机,默认交换机把消息存储在队列中,这个消息是被持久化到磁盘中的

关于消息持久性的注意事项

将消息标记为持久性并不能完全保证消息不会丢失。虽然它告诉 RabbitMQ 将消息保存到磁盘,但是当 RabbitMQ 接受消息并且还没有保存它时,仍然有很短的时间窗口。此外,RabbitMQ 不会对每条消息都执行fsync(2) ——它可能只是保存到缓存中而不是真正写入磁盘。持久性保证并不强,但对于我们简单的任务队列来说已经绰绰有余了。如果您需要更强的保证,那么您可以使用 发布者确认

8、消费者端自动应答消息方式、手动应答消息的两种方式

8.1、自动应答消息

解释:当消费者接收到消息之后,直接会向RabbitMQ服务端发送应答消息,然后RabbitMQ服务端就会进行消息删除,假设在消息处理过程中出现问题,消息也不会进行重新分配,毕竟消息已经被删除了,代码如下:
在这里插入图片描述

8.2、手动应答消息之单条应答(推荐)

默认情况下一个通道中可以存在多条消息,当消费者端将该条消息处理完毕,我们可以进行该消息的手动应答,代码如下:
在这里插入图片描述
其中message.getEnvelope().getDeliveryTag()是当前消息的标签,用于RabbitMQ服务端来确认消息是哪一个

8.3、手动应答消息之批量应答(不推荐)

上面提到默认情况下一个通道中可以存在多条消息,如果我们进行批量消息确认之后,当前通道中正在处理的消息出现了问题,那消息已经被确认了,因此消息不会在此分发给其他消费者进行处理,所以有可能造成消息丢失,虽然上面“手动应答消息之单条应答”会进行多次消息确认,但是这种资源浪费保证了消息可能完全消费,这是值得的

9、消费者端设置通道中最大消息数量(RabbitMQ服务端往通道中发送的消息数量)的作用

Work Queues模式下,将会有多个消费者来进行消息消费,但是消费者的能力不同,另外消息处理起来的难易程度也不同,所以我们设置RabbitMQ服务端往通道中发送的最大消息数量,以保证消息能够被合理的消费,这符合能者多劳、公平调度的思想,能更好的使用各个消费者的能力,代码如下:

在这里插入图片描述
当然这种设置也不是必须的,毕竟我们可以多接受几个消息,然后多线程来处理它们,之后单个确定消息被消费,这样可以充分利用消费者端的服务器性能

10、什么情况下消息会重新入列,也就是什么时候不会接收到消费者发送过来的ack消息确认?

  1. 消费者在没有发送 ack 的情况下死亡(其通道关闭、连接关闭或 TCP 连接丢失)
  2. 消费者处理消息时间超时(超时时间详解看:11、消费者始终不确认消息被消费,阐述RabbitMQ服务端的处理策略

11、消费者始终不确认消息被消费,阐述RabbitMQ服务端的处理策略

消费者确认消息的超时时间是30分钟,如果在这个时间段内消息没有被确认,那RabbitMQ服务端就认为消费者执行消息已经失败,然后将消息分发给其他消费者,当然超时时间也是可以改变的,如果需要改变,请看Delivery Acknowledgement Timeout

12、生产者可以直接指定接收消息的队列吗?

不可以,无论是简单模式还是复杂模式,生产者都是将消息发送给了交换机(简单模式和工作队列模式使用默认交换机),然后交换机根据路由把消息发送给消费者,消费者端只需要路由,消息将会发送给对应消费者,但是生产者无法直接指定接收消息的队列

13、为什么生产者端发送消息时一定要指定路由?

生产者端把消息发送到交换机中,交换机把消息发送到队列中,其中交换机和队列通过路由建立连接,所以生产者端发送消息的时候就需要将消息和路由绑定,然后消息到达交换机的时候,交换机可以清晰的知道这条消息将会发送到哪个队列中

14、消费者端接收到了消息,但是RabbitMQ控制台中的消息依然存在,原因是什么

  1. 消费者端开启了消息批量应答,并且设置了通道中可以同时存在的未消费消息数量大于1,但是消息数量小于同时存在的未消费消息数量,因此消费者端没有进行批量应答,所以RabbitMQ控制台中的消息依然存在;解释:这是我遇到的真实情况,之前和KG公司使用RabbitMQ传递消息的时候,他们就是遇到了该情况,后来发现设置的prefetchCount的值是10,参数设置方式如下:
    在这里插入图片描述

15、交换机的几种类型

交换机的类型有以下几种:direct、topic、headers、fanout

  1. direct:交换机根据路由键完全匹配关系来发送消息到队列,首先消息携带的有路由键,而交换机和队列也通过路由键进行绑定,当这两个路由键完全相等的时候,交换机就会把消息发送给该队列
  2. fanout:交换机会把消息发送给所有和该交换机绑定的队列,和路由键无关
  3. topic:消息路由键都是由单词组成,中间用.分隔,队列和交换机绑定的路由键可以使用通配符*和#

对比:

  1. 交换机使用topic模式,当队列使用 # 为路由键和交换机绑定时,它将接收所有消息,那么和交换机使用fanout模式一样
  2. 交换机使用topic模式,当队列中不使用*或者#特殊字符,那么和交换机使用direct模式一样

16、我们把消息发送到交换机上,但是没有任何队列和交换机相连,那么消息将何去何从?

消息将被丢弃

17、创建随机名称的临时队列有什意义?

平时为了队列持久化和消息持久化,我们会进行相关设置,但是有些时候我们不在乎队列持久化和消息持久化,我们只关心我们和交换机连接过程中获取到的消息,这就需要临时队列了,一旦断开交换机和消费者的连接,队列就会被自动删除,比如发布/订阅中举出的日志系统例子,我们只需要在连接上交换机的时候将消息实时展示在屏幕上,所以我们就可以使用临时队列来做这件事

18、basicQos()方法如何使用

官方的解释请点击我,该方法是一个重载方法,可以写一个参数,也可以写两个参数;

如果只写一个参数,代表每一个消费者在通道中的未确认消息的数量最大限制,当然这一个参数是0的话,那代表没有限制;

如果写两个参数,第二个参数是false的话代表每一个消费者在通道中的未确认消息数量的最大限制,第二个参数是true的话,代表通道中所有未确认消息数量的最大限制;

举例说明:

// 情况1:每一个消费者在通道中的未确认消息数量的最大限制
channel.basicQos(10);

// 情况2:没有任何限制
channel.basicQos(0);

// 情况3:通道限制是顶级限制,其次是消费者在通道中的未确认发消息的最大限制
// 每一个消费者在通道中的未确认消息数量的最大限制
channel.basicQos(10, false);
// 通道中所有未确认消息数量的最大限制
channel.basicQos(15, true);

19、basicReject()和basicNack()方法的区别

basicReject只能拒绝一条消息,但是basicNack可以拒绝一条或者多条消息,其中多条消息代表通道中的所有消息,我们用代码来说明一下吧,如下:

basicReject:

// 将该条消息拒绝掉,并且不放入队列
// 参数1:消息标签
// 参数2:是否重新放入队列
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);

basicNack:

// 将通道中的所有消息都拒绝掉,并且不放入队列
// 参数1:消息标签
// 参数2:是否批量确定;(1)true:代表批量确定,即通道中的所有消息都将被拒绝;(2)false:代表单条确定,即该条消息将被拒绝,不会影响通道中的其他消息
// 参数3:是否重新放入队列;(1)true:重新放入队列;(2)flase:不放入队列,成为死信
channel.basicNack(message.getEnvelope().getDeliveryTag(), true,false);

猜你喜欢

转载自blog.csdn.net/qq_42449963/article/details/124308287