【RabbitMQ】原生Api学习之入门篇

参考文献:《RabbitMQ实战指南》
参考资料: RabbitMQ官网教程
PS:文末会附上示例代码获取地址(码云)。

安装和其他相关博客可以参照如下:
【RabbitMQ】基础相关概念及安装(包含windows下的安装)
【RabbitMQ】Linux下的安装【极速版】
【RabbitMQ】Linux下的安装【详尽版】
【RabbitMQ】基础名词及控制台简单介绍


1.总体概述

首先我们这边为什么要学习原生Api呢,因为虽然RabbitMQ是已经比较成熟的技术了,就例如在SpringSpring Boot中皆可整合使用,使用原生无封装的代码的方式可以完成开发,使用Spring整合配置文件可以简化开发代码,使用Spring Boot中的注解开发可以更加提升效率,但是无论如何封装,升级,其本质不变(当然现在使用最多的肯定是在Spring Boot中整合使用),本博客内容旨在新手入门,以及打牢基础,原生的使用掌握了,封装的内容大都只是掌握简化用法罢了,还是注重基础。
SpringSpring Boot中的整合使用在后边其他博客都会有介绍。

2.创建工程并引入依赖

本文使用IDEA,创建一个无骨架的普通Maven工程,我们使用RabbitMQ时需要引入其jar包,使用Maven无疑是一个方便的选择(懒),当然我们也可以去下载jar直接引入到工程中。官网上有对应Java Client客户端的下载详情页面:RabbitMQ Java Client Library
简单截图了解一下:
5.x的版本需要支持JDK8以上,4.x版本需要JDK6以上,本文使用5.x版本,JDK1.8。是开源的。

在这里插入图片描述
截至目前最新版本5.10.0,提供了Mavne和Gradle的依赖坐标。等会可以直接copy。

在这里插入图片描述
以及最后,提供了Jar包下载的地址:

在这里插入图片描述
以及github源码工程地址:

在这里插入图片描述
写代码之前还是把这图撂在这,方便理解流程:

在这里插入图片描述

扫描二维码关注公众号,回复: 16868178 查看本文章

3.第一步:连接 RabbitMQ

书中概览:
在这里插入图片描述
这里我们使用的是ConnectionFactory:连接工厂
使用最新的客户端包:

在这里插入图片描述
下来我们创建一个生产者的代码包,书写连接Rabbitmq的代码:
这里我使用的是自创建的账号cfl和虚拟机/test,使用默认自带的用户guest和虚拟机/也是没问题的,创建的用户在这一篇最后有提到:【RabbitMQ】基础名词及控制台简单介绍

public static void main(String[] args) throws
            IOException, TimeoutException, NoSuchAlgorithmException, KeyManagementException, URISyntaxException {
    
    

        // 第一种方式,方法设定参数

        // 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 主机地址: 默认为 localhost
        connectionFactory.setHost("localhost");
        // 连接端口: 默认为 5672
        connectionFactory.setPort(5672);
        // 虚拟主机名称: 默认为 /
        connectionFactory.setVirtualHost("/test");
        // 连接用户名: 默认为guest
        connectionFactory.setUsername("cfl");
        // 连接密码: 默认为guest
        connectionFactory.setPassword("cfl");
        // 从工厂获得连接:
        Connection connection = connectionFactory.newConnection();

        // 第二种方式,使用URL方式
        ConnectionFactory connectionFactoryForURL = new ConnectionFactory();
		connectionFactoryForURL.setUri("amqp://userName:password@ipAddress:portNumber/virtualHost");

        // 用连接创建Channel
        Channel channel = connection.createChannel();

    }

在创建之后, Channel 可以用来发送或者接收消息了。

4.官网教程介绍(Java示例)

地址:RabbitMQ 官网教程
截至目前2020年底,官网教程中提供了七块内容,这一次我们的入门学习只学习前五块内容,为什么不学习后边两个,原因有二,其一,RPC的调用不太RabbitMQ,其二:Publisher Confirms不算是一种典型的模式,只能算是一种扩展,在发送方确认消息的服务到达情况,有兴趣的朋友可以下来自己学习,demo内容也比较简单。
我们主要了解2,3,4,5四块内容(也称之为RabbitMQ工作模式)加上第一个入门的示例。
RabbitMQ提供了多种客户端的操作,支持非常多的语言。如下所示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
另外最后,前排提示,官网上的这些教程如果是初学者的话,不建议跳过或者挑着看,这些教程中关于RabbitMQ组件的使用是一个循序渐进的过程,在下边的实例和介绍中也可以感受到知识层面的递进关系。

学习步骤从上到下:
hello world:生产者+队列+消费者
work queue:生产者+队列+多个消费者
Publish/Subscribe:生产者+交换机+队列+绑定+消费者
Routing:生产者+交换机+队列+绑定+路由键+绑定键+消费者
Topics:生产者+交换机+队列+绑定+路由键通配符+绑定键通配符+消费者

以及: 比较通用和常见的模式是最后两个高阶的内容RoutingTopics

5.官网教程之 “Hello World!” 及扩展【队列声明】+【消费消息的两种实现方式】

在这里插入图片描述


上边我们已经完成了RabbitMQ的连接和信息通道的创建,接下来我们需要去声明(创建)交换机队列,我们在程序中使用时需要保证其二者已经存在,否则会抛出异常。
这里在"Hello World!"教程中没有明确声明(创建)交换机,因为其使用了默认的交换机,这里我们也先省略掉交换机的创建过程,主要说明队列的创建,在之后的内容中说明关于交换机的创建。
这里对于交换机和队列的创建,官网提供的方法名为 声明:declare
下边代码为生产者 P:producer端代码:声明队列,并向其发送一条消息

// 声明(创建)一个队列
channel.queueDeclare("HELLO_WORLD_QUEUE", false, false, false, null);
String message = "Hello World!";
// 发送消息
channel.basicPublish("", "HELLO_WORLD_QUEUE", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
// 关闭资源
channel.close();
connection.close();

声明队列方法的参数如下:
在这里插入图片描述
这里直接附上书上的参数介绍,比较详细:
在这里插入图片描述
声明队列方法的重载方法不多,但有两个相似方法,这里做简单介绍:

在这里插入图片描述

1.queueDeclare()方法,没有参数,那都是使用默认值的,也称之为匿名队列,其创建的队列名称也是自动生成的一段乱码,类似amq.gen-LhQzlgv3GhDOv8PIDabOXA,是排他的、自动删除的、非持久化队列。
2.queueDeclare(…) 上边已经介绍过了。
3.queueDeclareNoWait(…) 方法,方法如其名,nowait,急性子方法,这四个方法中就这个方法没有返回值,其他均会返回创建队列的结果DeclareOk,而这个方法不需要,如果创建队列发生了异常,或者创建队列有延迟,则在使用时可能会出现问题,不建议使用此方法(心急吃不了热豆腐,何必呢( ̄▽ ̄)")。
4.queueDeclarePassive(…) 方法,比较常用,这个方法用来检测相应的队列是否存在。 如果存在则正常返回 ,如果不存在则抛出异常: 404 channel exception,参数为队列名称。

这里我们执行生产者端的代码,然后对应的去控制台查看:
发现队列创建成功,并且接收到了一条消息。

在这里插入图片描述
我们打开消息查看:

在这里插入图片描述
确实为我们之前发送的消息,没有问题。
发送消息的方法这里我们简单看一下,之后有章节详细说明:

在这里插入图片描述
有四个参数,这里我们没有专门去定义使用交换机,所以交换机参数为空字符串,routingKey也还没介绍,这里先直接用吧,后边会详细介绍,这里值是队列的名称,第三个参数为扩展属性,暂时没有用到,第四个参数就是我们要发送的消息的消息体了,需要传入一个byte数组。

接下来完成消费者 C:consumer 端代码:声明队列,并监听队列获取消息

		// 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 主机地址: 默认为 localhost
        connectionFactory.setHost("localhost");
        // 连接端口: 默认为 5672
        connectionFactory.setPort(5672);
        // 虚拟主机名称: 默认为 /
        connectionFactory.setVirtualHost("/test");
        // 连接用户名: 默认为guest
        connectionFactory.setUsername("cfl");
        // 连接密码: 默认为guest
        connectionFactory.setPassword("cfl");
        // 从工厂获得连接:
        Connection connection = connectionFactory.newConnection();

        // 用连接创建Channel
        Channel channel = connection.createChannel();

        // 声明一个队列
        channel.queueDeclare("HELLO_WORLD_QUEUE", false, false, false, null);
        // 1.官网示例:使用DeliverCallback接口接收和消费消息
        // 创建监听回调实例
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
    
    
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };
        // 开始监听接收并消费
        channel.basicConsume("HELLO_WORLD_QUEUE", true, deliverCallback, consumerTag -> {
    
     });

DeliverCallback 接口是一个函数式接口,使用起来比较方便且单纯

在这里插入图片描述
我们来执行上述代码查看结果:
在这里插入图片描述
我们再去查看控制台,发现消息已经被消费:
在这里插入图片描述
我们可以在Consumers Tab栏中看到此时队列有一个消费者:
在这里插入图片描述


上边是官方的示例代码实现,不过并不是只有这一种方式可以消费消息,我们还有别的功能更多的实现方式,例如实现Consumer消费者接口,这里我们使用Consumer接口的默认实现类DefaultConsumer,上代码:
		// 声明一个队列
        channel.queueDeclare("HELLO_WORLD_QUEUE", false, false, false, null);

		// 2.使用Consumer消费者接口的默认实现类DefaultConsumer完成消息的接受和消费
        // 创建消费者;并设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    
            @Override
            /**
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息路由key:routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        /**
         * 参数1:队列名称
         * 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume("HELLO_WORLD_QUEUE", true, consumer);

上述代码同样可以完成消息的接收,并且在Consumer接口中有更多其他的方法后边我们可以实现并使用:

在这里插入图片描述


最后在这里补充一下,在上述生产者和消费者代码中,我们都声明创建了队列,这样是没有问题的,我们只需要保证队列在向此队列发送监听获取此队列信息存在即可,假如上述代码中,我们先执行原先不变的生产者代码,其创建了队列,而消费者端不去声明队列也是可以得,不会报错,并且重复创建和声明也是没有问题的,同时,如果你提前创建好了队列,在消费者和生产者端都不进行队列声明也是可以的,在队列已知的情况下,这么提前“预热”队列也是一个不错的选择。
附上一段书中的说明:
在这里插入图片描述

6.官网教程之 Work queues

在这里插入图片描述


Work queues翻译过来就是工作队列模式
这里依旧还没有提到交换机的概念,使用的依旧是默认自带的交换机。这个模式和上边hello world的唯一区别就是,这里一个队列有多个消费者。官网的描述是,假设队列传递的是一个又一个工作,我们需要快速的完成工作,此时可以使用工作队列模式,相当于有多个工人在自己的线路上做自己的工作,这里消息的分发是类似负载均衡的感觉,这里的过程我们简单描述一下:

我们分别将第一个例子中的消费者代码复制两份,同时监听一个队列,此时队列中没有消息,两个消费者都在等待消息的到来
然后我们启动生产者,依次向队列发送十条消息,然后两个消费者会分别接收到五条消息,并且消息间是间隔的,类似C1消费者接收到第一条消息,C2消费者接收到第二条消息,C1消费者接收到第三条消息,C2消费者接收到第四条消息,等等以此类推。。。

这里我们直接上代码,先是消费者端的两个,唯一区别就是我们这里打印的字符串不同:

public static void main(String[] args) throws
            IOException, TimeoutException, NoSuchAlgorithmException, KeyManagementException, URISyntaxException {
    
    

        // 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 主机地址: 默认为 localhost
        connectionFactory.setHost("localhost");
        // 连接端口: 默认为 5672
        connectionFactory.setPort(5672);
        // 虚拟主机名称: 默认为 /
        connectionFactory.setVirtualHost("/test");
        // 连接用户名: 默认为guest
        connectionFactory.setUsername("cfl");
        // 连接密码: 默认为guest
        connectionFactory.setPassword("cfl");
        // 从工厂获得连接:
        Connection connection = connectionFactory.newConnection();

        // 用连接创建Channel
        Channel channel = connection.createChannel();

        // 声明一个队列
        channel.queueDeclare("Work_Queue", false, false, false, null);

        // 2.使用Consumer消费者接口的默认实现类DefaultConsumer完成消息的接受和消费
        // 创建消费者;并设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                //收到的消息
                System.out.println("WorkQueue1 接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        channel.basicConsume("Work_Queue", true, consumer);

    }

在这里插入图片描述
启动两个消费者,等待消息的到来。
然后我们编写生产者,其实代码几乎和上一个例子一致,这里只是多发送了几条消息而已,我们只上修改部分的代码:

		// 声明一个队列
        channel.queueDeclare("Work_Queue", false, false, false, null);
        String message = "work queue ";
        for (int i = 0; i < 10; i++) {
    
    
            channel.basicPublish("", "Work_Queue", null, (message + i).getBytes());
            System.out.println(" [x] Sent '" + (message + i) + "'");
        }

然后我们启动生产者,生产者这边控制台打印为:

在这里插入图片描述
然后我们分别查看两个消费者的控制台打印:
消费者1:

在这里插入图片描述
消费者2:

在这里插入图片描述
其消息的分发与接收确实是一种均衡的方式。多个消费者(类似多线程)可以加快我们消息的处理速度。


7.官网教程之 Publish/Subscribe 及扩展【交换机声明】+【队列绑定】

在这里插入图片描述


Publish/Subscribe 翻译过来就是 发布/订阅 模式,简单来说就是你在微信上关注了某个公众号,或者你在微博关注了某个博主,这就是一个订阅过程,当这个公众号/博主发布了新文章,这就是发布的过程,然后我们就可以看见并阅读等等,这里公众号/博主就是生产者,我们订阅的人就是消费者。
此模式中有新的内容出现,那就是图中的X,也就是我们的交换机。从一小节内容开始,这几节的内容都是围绕交换机的类型展开的,并不会有很大的变化,交换机的共通功能就是接收消费者方发送的消息,然后将其分发,根据其类型的不同,分发消息时的策略也不一致,交换机有常见的三种类型

Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向,把消息交给符合指定routing key 的队列
Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

这一小节我们使用的交换机类型为Fanout,广播模型。其会将所有接收到的消息分发至所有与自己绑定的队列上。
这里我们同样将此小节的代码示例内容做一个简单的描述:

首先,一个生产者,两个消费者,这个数量不变,这次我们需要创建(声明)一个交换机,并且其类型为Fanout,然后声明两个队列,队列和交换机的声明先后顺序无所谓,在保证队列和交换机都声明完成后,再完成队列和交换机的绑定,两个队列分别都需要和交换机绑定,绑定完成即可。最后我们启动两个消费者分别监听消费一个队列,再启动生产者发送一条测试消息,结果就是两个队列都各自收到此条测试消息,然后两个消费者分别将消息消费掉。

我们开始写代码,首先是生产者端的代码,我们省略掉连接部分的代码,直接开始业务逻辑代码,完整代码最后文末会有链接地址:

		// 声明队列1
        channel.queueDeclare("Pub_Sub_Queue1", false, false, false, null);// 声明队列1
        // 声明队列2
        channel.queueDeclare("Pub_Sub_Queue2", false, false, false, null);

        /*
        AMQP.Exchange.DeclareOk exchangeDeclare(
            String exchange,
            BuiltinExchangeType type,
            boolean durable,
            boolean autoDelete,
            boolean internal,
            Map<String, Object> arguments) throws IOException;
        */
        // 声明一个类型为FANOUT的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Pub_Sub_Exchange", BuiltinExchangeType.FANOUT, false, false, null);

        /*
        Queue.BindOk queueBind(
            String queue,
            String exchange,
            String routingKey,
            Map<String, Object> arguments
        ) throws IOException;
        */
        // 将两个队列分别和交换机绑定,绑定方法全部参数如上
        // 这里我们没有使用到routingKey,在下一章会说到,这里是传空字符串
        channel.queueBind("Pub_Sub_Queue1", "Pub_Sub_Exchange", "", null);
        channel.queueBind("Pub_Sub_Queue2", "Pub_Sub_Exchange", "", null);

        // 最后我们发送一条消息
        String message = "hello Publish and Subscribe !!!";
        /*
        * 参数1:指定交换机名称
        * 参数2:指定路由键routingKey为空字符串
        * 参数3:指定扩展参数暂无
        * 参数4:指定发送的消息
        */
        channel.basicPublish("Pub_Sub_Exchange", "", null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");

其中BuiltinExchangeType类是交换机类型定义的枚举,如下,里边定义了四种交换机类型:
这四种交换机类型后边我们也会有详细的使用和说明。

在这里插入图片描述
下来我们看一下交换机的声明方法:
其也有多个重载方法,不过当然有一个参数最多的会覆盖所有情况,最多参数的方法在代码注释中已经给出,这里我们可以再看一下所有重载方法:其同样也有NoWait和Passive的方法,用法和队列的类似名字的方法一致。

在这里插入图片描述
在这里插入图片描述
详细参数解析如下,直接附上书中的说明:

在这里插入图片描述


接下来我们再看一下队列的绑定方法,参数稍微比上边那个少一点:

在这里插入图片描述
在这里插入图片描述
详细参数的解析如下,以及可以将已经绑定的队列从交换机解绑:

在这里插入图片描述


好的,上边方法参数内容介绍完毕,这里我们把代码跑起来去RabbitMQ的控制台查看效果,应该是会创建一个交换机和两个队列,并且交换机和队列是绑定的,同时此时,两个队列中分别有一条刚才发送的消息,首先我们跑完代码看代码控制台确认发送成功:

在这里插入图片描述
接下来我们打开RabbitMQ的控制台分别查看交换机和队列的情况:
首先是队列: 两个队列分别有一条消息
在这里插入图片描述
我们打开队列确认一下消息: 没有问题
在这里插入图片描述
下来我们确认一下交换机: 直接当场逮捕,并发现其type为fanout。
在这里插入图片描述
我们再点进去交换机的详情页,查看其队列的绑定情况: 确认其与上述两个队列绑定成功

在这里插入图片描述


这里生产者端的代码就告一段落,回到消费者端,其实啊,消费者端这里和前一个例子的代码几乎相差无几,消费端的逻辑都只是订阅队列然后获取并打印消息,无非这里在发布订阅模式的代码里订阅的队列不同,多了一段交换机的声明及队列的绑定过程而已,这里我们不做过多描述,简单展示一下其中一个消费者的代码,然后附上控制台的结果输出即可(依旧省略连接的代码):

		// 声明一个队列
        channel.queueDeclare("Pub_Sub_Queue1", false, false, false, null);

        // 声明一个类型为FANOUT的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Pub_Sub_Exchange", BuiltinExchangeType.FANOUT, false, false, null);

        // 将队列和交换机绑定
        channel.queueBind("Pub_Sub_Queue1", "Pub_Sub_Exchange", "", null);

        // 2.使用Consumer消费者接口的默认实现类DefaultConsumer完成消息的接受和消费
        // 创建消费者;并设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                //收到的消息
                System.out.println("Pub_Sub_Queue1 接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        channel.basicConsume("Pub_Sub_Queue1", true, consumer);

控制台打印结果:

在这里插入图片描述
在这里插入图片描述

8.官网教程之 Routing 及扩展【绑定键和路由键】

在这里插入图片描述


Routing就是路由的意思,上边我们说过后边的内容其实就是交换机类型不同的模型,没错,上一节内容交换机的类型是fanout,这一节交换机的类型是direct,了解java web的童鞋们肯定都知道redirect,重定向,那direct的意思自然就是定向了,这里这个交换机也称之为定向交换机,结合路由定向这两个词,我们来简单概述一下direct类型交换机的功能特点:和上一节fanout不同的是,fanout相当于是无差别扇形广播消息,所有和交换机绑定的队列均可以收到消息,定向的初衷就是对上述”普照“加上条件,即使交换机上绑定了多个队列,某条消息也会被转发至特定的队列,而不是所有队列都会接收到此消息。
具体实现上述功能我们需要结合图片进行说明:

首先,将队列和交换机绑定时,可以指定我们的绑定键,在上一节中我们介绍了绑定的方法和参数,但是在上一节我们没有指定绑定键,绑定时指定了空字符串,在这一节中我们会开始指定绑定key,如下图中binding key1和2,亦如上边图片中的orange,black,green。
在这里插入图片描述
在rabbitmq中其实是没有binding key这个概念的,绑定和发送时指定的key被统称为路由key,但是我们为了方便理解,将其划分为绑定key和路由key,如下:
在这里插入图片描述
在这里插入图片描述
路由键就是在生产者发送消息时需要指定的。

最终工作原理是:
direct交换机接收到消息之后,将消息中的路由key和所有绑定的队列绑定时指定的绑定key进行比较,完全匹配的话,会将消息转发至此队列,如上图中有三个绑定key:orange,black,green
如果我们发送消息时指定routing key 为orange,则消息最终会被发送至队列Q1,而如果指定的是black或green,则会被发送至队列Q2,队列可以和交换机绑定多次,每次可以指定不同的绑定key,最终一个队列和交换机间可以持有多个不同的绑定key,其相互间是或者的关系。以及,队列间可以绑定同样的绑定key,这时就类似于降级为上一节模型,会将消息发送至所有满足绑定key的队列中,例如下图,发送路由key为warning的消息,因为此时两个队列都指定了绑定key为warnig,所以两个队列均会收到此消息。

在这里插入图片描述


逼话少说,我们来看代码如何实现,就使用上边orange,black,green的例子吧,简单易懂,这里的代码和上一节的内容也十分相似,这里我们再来做一个代码实现场景的简单描述:

1.还是一个生产者,两个消费者,一个交换机两个队列。
2.交换机类型为redirect。
3.队列1和交换机绑定一次,绑定时指定绑定key为orange。
4.队列2和交换机绑定两次,绑定时指定绑定key分别为black和green。
5.最后我们发送三条消息,指定其路由key分别为orange,black,green,并查看消息的到达消费情况。

下来我们看生产者端的代码(省略创建连接的代码):

		// 声明队列1
        channel.queueDeclare("Routing_Queue1", false, false, false, null);
        // 声明队列2
        channel.queueDeclare("Routing_Queue2", false, false, false, null);

        /*
        AMQP.Exchange.DeclareOk exchangeDeclare(
            String exchange,
            BuiltinExchangeType type,
            boolean durable,
            boolean autoDelete,
            boolean internal,
            Map<String, Object> arguments) throws IOException;
        */
        // 声明一个类型为Direct的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Routing_Exchange", BuiltinExchangeType.DIRECT, false, false, null);

        /*
        Queue.BindOk queueBind(
            String queue,
            String exchange,
            String routingKey,
            Map<String, Object> arguments
        ) throws IOException;
        */
        // 将两个队列分别和交换机绑定,绑定方法全部参数如上
        // 队列1和交换机绑定,绑定key为orange。
        channel.queueBind("Routing_Queue1", "Routing_Exchange", "orange", null);

        // 队列2和交换机绑定,绑定key分别为black和green。
        channel.queueBind("Routing_Queue2", "Routing_Exchange", "black", null);
        channel.queueBind("Routing_Queue2", "Routing_Exchange", "green", null);

        // 最后我们发送三条消息,分别指定routingKey
        String message_orange = "hello Routing and orange !!!";
        String message_black = "hello Routing and black !!!";
        String message_green = "hello Routing and green !!!";
        /*
         * 参数1:指定交换机名称 Routing_Exchange
         * 参数2:指定路由键routingKey
         * 参数3:指定扩展参数暂无
         * 参数4:指定发送的消息
         */
        channel.basicPublish("Routing_Exchange", "orange", null, message_orange.getBytes());
        System.out.println(" [x] Sent '" + message_orange + "'");

        channel.basicPublish("Routing_Exchange", "black", null, message_black.getBytes());
        System.out.println(" [x] Sent '" + message_black + "'");

        channel.basicPublish("Routing_Exchange", "green", null, message_green.getBytes());
        System.out.println(" [x] Sent '" + message_green + "'");

然后我们执行代码,查看控制台确认发送成功:
在这里插入图片描述
OK,没有问题,下来我们再去RabbitMQ的后台查看交换机和队列的情况:
首先我们查看交换机:
在这里插入图片描述
查看交换机绑定的队列详情: 也没有问题
在这里插入图片描述
接下来去查看队列和已经发送到队列的消息:
在这里插入图片描述
查看队列1:
在这里插入图片描述
查看队列2:
在这里插入图片描述
以上都符合我们的预想,至于消费端的代码,依旧没有啥变化,我都不想写了。。。这里消费端没什么逻辑,依照惯例这里贴一部分消费者的代码,详情可以去下载代码查看:
消费者端部分代码:

		// ************************************** 消费者1 **************************************
		// 声明一个队列
        channel.queueDeclare("Routing_Queue1", false, false, false, null);

        // 声明一个类型为Direct的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Routing_Exchange", BuiltinExchangeType.DIRECT, false, false, null);

        // 将队列和交换机绑定
        channel.queueBind("Routing_Queue1", "Routing_Exchange", "orange", null);

        // 2.使用Consumer消费者接口的默认实现类DefaultConsumer完成消息的接受和消费
        // 创建消费者;并设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                //收到的消息
                System.out.println("Consumer_RoutingQueue1 接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        channel.basicConsume("Routing_Queue1", true, consumer);
        
		// ************************************** 消费者2 **************************************
		// 声明一个队列
        channel.queueDeclare("Routing_Queue2", false, false, false, null);

        // 声明一个类型为Direct的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Routing_Exchange", BuiltinExchangeType.DIRECT, false, false, null);

        // 将队列和交换机绑定
        channel.queueBind("Routing_Queue2", "Routing_Exchange", "black", null);
        channel.queueBind("Routing_Queue2", "Routing_Exchange", "green", null);

        // 2.使用Consumer消费者接口的默认实现类DefaultConsumer完成消息的接受和消费
        // 创建消费者;并设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                //收到的消息
                System.out.println("Consumer_RoutingQueue2 接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        channel.basicConsume("Routing_Queue2", true, consumer);

我们分别启动两个消费者,并查看消息的消费情况:
消费者1:
在这里插入图片描述
消费者2:在这里插入图片描述
以上便是Routing模式的MQ实现,是一种比较典型的实现,集合了几乎MQ所有的基础组件,是一个完整的生产者消费者MQ模型,我们在实际应用中的使用基本上都是基于此模式或Topics模式的,在学习完后一章节后你会发现,其实Routing和Topics两个模式并没有实质上的区别。两者的相似程度高达90%以上。


9.官网教程之 Topics

在这里插入图片描述


Topics,翻译过来就是主题,话题
上边也提到过,这一个模式其实也是根据不同的交换机类型来展开的,这里的交换机类型为topic,和上一章极度相似,上一张中direct交换机提供的功能是:发送时去匹配路由键和绑定键,是完全匹配的模式,二者必须完全一致,但是大家都知道,常言道:有等于就必有约等于!
如上图中大家也可也稍有猜测到,topic交换机提供给我们的就是路由键和绑定键的模糊匹配模式,这里的模糊匹配指的并不是我们传统的像是正则表达式,他要更加简单一点。
topic交换机的模糊匹配机制是使用了通配符,所以我们也称Topics模式为通配符模式
在这里插入图片描述
其实通配符也就可以理解为是一种占位符,具体如下:
在这里插入图片描述
官网:
在这里插入图片描述
这里再补上一个书上的例子,这个会稍微复杂一点:
在这里插入图片描述
好了,基本上这一节的内容都在通配符上了,我们做一个简单的代码示例,这里还是先介绍一下实例内容:

1.还是一个生产者,两个消费者,一个交换机两个队列。
2.交换机类型为topic。
3.队列1和交换机,绑定时指定绑定key为 rabbit.*
4.队列2和交换机,绑定时指定绑定key为 rabbit.#
5.最后我们发送三条消息,指定其路由key分别为rabbit,rabbit.mq,rabbit.mq.cfl,并查看消息的到达消费情况。
6.队列1应该会收到一条消息,而队列2将会三条消息。

OK,我们直接来看代码,首先是生产者端的:

		// 声明队列1
        channel.queueDeclare("Topics_Queue1", false, false, false, null);
        // 声明队列2
        channel.queueDeclare("Topics_Queue2", false, false, false, null);

        /*
        AMQP.Exchange.DeclareOk exchangeDeclare(
            String exchange,
            BuiltinExchangeType type,
            boolean durable,
            boolean autoDelete,
            boolean internal,
            Map<String, Object> arguments) throws IOException;
        */
        // 声明一个类型为TOPIC的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Topics_Exchange", BuiltinExchangeType.TOPIC, false, false, null);

        /*
        Queue.BindOk queueBind(
            String queue,
            String exchange,
            String routingKey,
            Map<String, Object> arguments
        ) throws IOException;
        */
        // 将两个队列分别和交换机绑定,绑定方法全部参数如上
        // 队列1和交换机绑定,绑定key为rabbit.*
        channel.queueBind("Topics_Queue1", "Topics_Exchange", "rabbit.*", null);

        // 队列2和交换机绑定,绑定key为rabbit.#
        channel.queueBind("Topics_Queue2", "Topics_Exchange", "rabbit.#", null);

        // 最后我们发送三条消息
        String message1 = "message1 the routingKey is rabbit !!!";
        String message2 = "message2 the routingKey is rabbit.mq !!!";
        String message3 = "message3 the routingKey is rabbit.mq.cfl !!!";
        /*
         * 参数1:指定交换机名称 Topics_Exchange
         * 参数2:指定路由键routingKey
         * 参数3:指定扩展参数暂无
         * 参数4:指定发送的消息
         */
        // 分别指定routingKey
        channel.basicPublish("Topics_Exchange", "rabbit", null, message1.getBytes());
        System.out.println(" [x] Sent '" + message1 + "'");

        channel.basicPublish("Topics_Exchange", "rabbit.mq", null, message2.getBytes());
        System.out.println(" [x] Sent '" + message2 + "'");

        channel.basicPublish("Topics_Exchange", "rabbit.mq.cfl", null, message3.getBytes());
        System.out.println(" [x] Sent '" + message3 + "'");

我们跑起来代码,查看idea控制台查看发送情况:
在这里插入图片描述

OK,没有问题,接下来我们去查看RabbitMQ的控制台
当场逮捕虚拟机:
在这里插入图片描述
逼其招供绑定的队列:
在这里插入图片描述
没有问题,接下来我们去查看队列和消息:
首先是队列1:
在这里插入图片描述
以及队列中的消息:
在这里插入图片描述
接下来我们看队列2:
在这里插入图片描述
以及其内部接收到的消息:
在这里插入图片描述


OK,完全莫得问题,接下来我们还是看消费者的代码,依旧同上边几节一样,由于消费者端暂时没有什么逻辑代码,依旧附上一部分的代码,我们查看消费结果即可,完整代码在文末有链接:
消费者端部分代码:

		// ************************* 消费者1 *************************
		// 声明一个队列
        channel.queueDeclare("Topics_Queue1", false, false, false, null);

        // 声明一个类型为TOPIC的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Topics_Exchange", BuiltinExchangeType.TOPIC, false, false, null);

        // 将队列和交换机绑定
        channel.queueBind("Topics_Queue1", "Topics_Exchange", "rabbit.*", null);

        // 2.使用Consumer消费者接口的默认实现类DefaultConsumer完成消息的接受和消费
        // 创建消费者;并设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                //收到的消息
                System.out.println("Consumer_TopicsQueue1 接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        channel.basicConsume("Topics_Queue1", true, consumer);
		
		// ************************* 消费者2 *************************
		// 声明一个队列
        channel.queueDeclare("Topics_Queue2", false, false, false, null);

        // 声明一个类型为TOPIC的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Topics_Exchange", BuiltinExchangeType.TOPIC, false, false, null);

        // 将队列和交换机绑定
        channel.queueBind("Topics_Queue2", "Topics_Exchange", "rabbit.#", null);

        // 2.使用Consumer消费者接口的默认实现类DefaultConsumer完成消息的接受和消费
        // 创建消费者;并设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                //收到的消息
                System.out.println("Consumer_TopicsQueue2 接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        channel.basicConsume("Topics_Queue2", true, consumer);

分别启动两个消费者,查看消息消费情况:
消费者1:
在这里插入图片描述
消费者2:
在这里插入图片描述

10.扩展之 【Headers 交换机】+【RabbitMQ工作模式总结】

了解完上述几个MQ模式以后,想必大家都可以对MQ有一个总体上的认知,特别是关于交换机,基于交换机的类型,我们可以做不同的事情,这里我们重新看一下交换机有哪几个类型:
在这里插入图片描述
是四大类型,上边我们在学习中唯一没有使用到的是headers交换机,为什么官网也没有介绍这个交换机呢?我当时也很疑惑,后来书上是这么解释的:
在这里插入图片描述
这里书中提到的 发送消息内容中的headers属性,是指发送消息时我们自己来指定的扩张属性,如下:在进阶内容中我们会对各种扩张属性内容做使用和说明。
在这里插入图片描述
因为其性能原因和实用性,没有被广泛使用,了解即可。
以及在AMQP协议中还提到了System和自定义类型交换机,书上不予评述,那我也就先不了解了,放你一马。
最后,这里我们对所有模式做一个总结:

模式总结,RabbitMQ工作模式:
1:简单模式 HelloWorld 一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。
2:工作队列模式 Work Queue 一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。
3:发布订阅模式 Publish/subscribe 需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列。
4:路由模式 Routing 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列。
5:通配符模式 Topic 需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列。


11.扩展之 【发送消息详解】

在RabbitMQ中我们发送一条消息是最基本的操作了,上边的代码实例中我们也有简单的使用过,这里我们把发送消息的方法做一个详细的说明,首先发送消息的方法是Channel的basicPublish,其有多个重载方法:
在这里插入图片描述
在上边的实例代码中我们都使用的是第一个参数最少的方法,参数我们简单复习一下:
在这里插入图片描述
在这里插入图片描述
props的内容我们会在后边进阶篇中着重介绍。
发送消息代码如下:

/*
 * 参数1:指定交换机名称 Topics_Exchange
 * 参数2:指定路由键routingKey
 * 参数3:指定扩展参数暂无
 * 参数4:指定发送的消息
 */
channel.basicPublish("Topics_Exchange", "rabbit", null, message1.getBytes());

后边两个参数多一些的方法中,我们直接在这里介绍方法参数最多的第三个方法,因为其已经包含了第二个的参数,这里我们先copy过来方法注释做一个简单的介绍:
在这里插入图片描述
这里其实和上边的相比就是多了两个参数:mandatoryimmediate
二者都是布尔类型的值,都是一个flag标识。
在这里插入图片描述
首先我们来看 mandatory 参数:
直接上书上的描述:
在这里插入图片描述

简单描述一下代码场景:
我们使用DIRECT交换机,绑定一个绑定key是rabbit的队列,然后我们发送两条路由key无法和绑定key匹配的两个不相干的key,两次发送消息分别将mandatory设置为true和false,然后使用ReturnListener将回退的消息获取并打印至控制台,这里应该只有一条消息被我们获取并打印,mandatory设置false的消息会被直接丢弃。

OK,直接上代码:

    public static void main(String[] args) throws
            IOException, TimeoutException, NoSuchAlgorithmException, KeyManagementException, URISyntaxException {
    
    

        // 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 主机地址: 默认为 localhost
        connectionFactory.setHost("localhost");
        // 连接端口: 默认为 5672
        connectionFactory.setPort(5672);
        // 虚拟主机名称: 默认为 /
        connectionFactory.setVirtualHost("/test");
        // 连接用户名: 默认为guest
        connectionFactory.setUsername("cfl");
        // 连接密码: 默认为guest
        connectionFactory.setPassword("cfl");
        // 从工厂获得连接:
        Connection connection = connectionFactory.newConnection();

        // 用连接创建Channel
        Channel channel = connection.createChannel();
        channel.addReturnListener(new ReturnListener() {
    
    
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                String message = new String(body);
                System.out.println("return 的消息是:" + message);
            }
        });

        // 声明队列
        channel.queueDeclare("Mandatory_Queue1", false, false, false, null);

        /*
        AMQP.Exchange.DeclareOk exchangeDeclare(
            String exchange,
            BuiltinExchangeType type,
            boolean durable,
            boolean autoDelete,
            boolean internal,
            Map<String, Object> arguments) throws IOException;
        */
        // 声明一个类型为DIRECT的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("Mandatory_Exchange", BuiltinExchangeType.DIRECT, false, false, null);

        /*
        Queue.BindOk queueBind(
            String queue,
            String exchange,
            String routingKey,
            Map<String, Object> arguments
        ) throws IOException;
        */
        // 队列和交换机绑定,绑定key为rabbit
        channel.queueBind("Mandatory_Queue1", "Mandatory_Exchange", "rabbit", null);

        String message1 = "test with mandatory true !!!";
        String message2 = "test with mandatory false !!!";
        /*
         * 参数1:指定交换机名称 Topics_Exchange
         * 参数2:指定路由键routingKey
         * 参数3:指定扩展参数暂无
         * 参数4:指定发送的消息
         */
        // 分别指定routingKey
        channel.basicPublish("Mandatory_Exchange", "nop", true, false, null, message1.getBytes());
        System.out.println(" [x] Sent '" + message1 + "'");

        channel.basicPublish("Mandatory_Exchange", "cfl", false, false, null, message2.getBytes());
        System.out.println(" [x] Sent '" + message2 + "'");

        // 关闭资源
        // channel.close();
        // connection.close();

    }

我们将代码跑起来,在控制台上查看结果:
在这里插入图片描述
没有问题,我这里又再试了一下,使用topic交换机也没有问题:
兄弟们,上边代码中有个坑,就是,现在我们测试的是消息发送后匹配失败的回退,如果你在消息回退回来之前将channel连接断开了,你是收不到退回的消息的…我们之前是发完消息就自然的将其关闭了,这里测试时是坚决不能关闭的,如果只是一条两条消息可能看不出来差别,如果你发多条不符合条件的数据,你很可能接收不到后边几条退回的消息。


至于immediate参数
RabbitMQ 3.0 版本开始去掉了对 imrnediate 参数的支持,对 RabbitMQ 官方解释是:immediate 参数会影响镜像队列的性能,增加了代码的复杂性,建议采用 TTL + DLX 的方法替代。TTL + DLX 是过期时间和死信队列,这个我们之后别的博客会详细说明。
所以这里immediate就不做说明了。


12.扩展之 【消费消息的两种模式】

直接先上概念:
在这里插入图片描述


下来我们对这两种消费方式做一个详细说明:

  1. 推Push 模式:Basic.Consume

其实这个Basic.Consume我们上边代码中也一直在用,其使用到的类如下:
分别是Consumer接口和其默认实现类DefaultConsumer
在这里插入图片描述
我们在上边代码中是创建了DefaultConsumer,重写了其消费方法handleDelivery:

DefaultConsumer consumer = new DefaultConsumer(channel){
    
    

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
        //收到的消息
        System.out.println("Consumer_TopicsQueue1 接收到的消息为:" + new String(body, "utf-8"));
    }
};

channel.basicConsume("Topics_Queue1", true, consumer);

下来我们看一下basicConsume的有哪些重构方法和参数:
重构方法:多的吓人
在这里插入图片描述

参数介绍:
其中autoAck这个我们会在后边进阶中说明,这里我们先设置为true。
在这里插入图片描述
这里consumerTag,是因为我们一个channel是允许多个消费者存在的,这里不同的消费者需要区分开来。
以及,当我们使用Consumer或DefaultConsumer,其是提供有很多方法的,可以根据需要去实现必要的方法:
在这里插入图片描述
以及最后提到的消费者端的线程安全问题:
在这里插入图片描述


  1. 拉Pull 模式:Basic.Get

在这里插入图片描述
推模式,是一种持续订阅的消费模型,一次可以获取多个消息,并且可以持续获取,而这里拉模式特点就是一次只能获取一条消息,显然这里拉模式的这种实现模型对业务有着很大的要求,并且效率上有着限制,其也只有一个方法:(这里autoAck我们在后边进阶内容中说明)
在这里插入图片描述
主要的参数也就两个,第一个是队列的名称,第二个是 是否自动确认。
由于拉模式的使用局限性比较高,这里就简单介绍一下,不做代码示例了,最后附上书中总结要点:
在这里插入图片描述
注:其中Basic.Qos内容也是同autoAck一样在进阶内容中说明。


13.扩展之 【关闭连接】+【队列和交换机的删除】

关于连接的关闭没有太多补充内容,直接上书上的说明:
在这里插入图片描述
在这里插入图片描述


在上边的各个工作模式中,我们已经声明(创建)了各种交换机和队列,常言道,有创建就有删除!
这里我们简单看一下RabbitMQ给我们提供的删除队列和交换机的方法:
首先是交换机的删除:
在这里插入图片描述
在这里插入图片描述

主要参数是两个:
1.exchange : 交换机的名称
2.ifUnused : 是true的话,只会在交换机没有使用时将其删除,如果是false,那就是强制删除,无论其状态。

下来是队列的删除:
在这里插入图片描述
在这里插入图片描述

这里队列删除的参数比上边交换机的删除多了一个参数,是ifEmpty,这里从名字就可以知道其作用,是用来判断队列中是否还存在消息的,如果是true,则只有当队列中没有消息时才会删除,如果是false,则是强制删除,无关是否有消息。
至于两个删除的Nowait方法,在上方队列和交换机的声明时已有说明,道理是一样的,详情可以回翻参照。

14.扩展之 【交换机相互绑定和解绑】+【备份交换机】

上边我们演示过交换机和队列的绑定,这是最基础的绑定方式,也是最常用的,但其实交换机之间也是可以绑定的,就相当于在之前交换机和队列的绑定间加了一个过滤器一样,具体的用法也是一致的,这种中间再加交换机的场景适合于更加复杂的业务场景,我们现来看一下其绑定方法:
在这里插入图片描述
在这里插入图片描述
这里简单介绍一下参数:
1.destination 目标交换机(目的地)
2.source 源交换机
3.routingKey 交换机之间的绑定key
4.arguments 扩展参数,是一个map集合

书上的图画的不错,拿来看看:
在这里插入图片描述


下来我们了解一下备份交换机,首先还是概念性的内容,我们看书上的说明:
在这里插入图片描述
我们在上边在消息的发送时也说明过mandatory这个参数,其作用是在交换机没有找到符合路由条件的队列时,会将消息返还给生产者,生产者端的channel需要添加一个监听器来获取消息并对其进行自定义的处理,可能这个步骤稍微有一点繁琐,备份交换机其实也可以完成这么一个简单的工作,不过备份交换机的实现是默认的,他默认的接受了回退的消息,然后转发给其他队列存储或者等等,其可以理解为mandatory参数模式的一个默认实现,如果我们对回退的消息有自己独特的处理业务的话,还是建议使用监听器获取消息并自行处理回退的消息。

下来我们来看如何实现备份交换机,这里我们使用添加alternate-exchange参数的方式来实现:
惯例,先说明一下我们测试代码的逻辑:

首先我们声明两个队列,一个普通队列normalQueue,一个绑定在备份交换上的队列unroutedQueue
声明两个交换机,一个是普通交换机normal_Exchange,类型为DIRECT,一个是备份交换机myAe
然后绑定队列和交换机,注意这里在声明normal_Exchange时,我们传入扩展参数alternate-exchange,让其绑定上备份交换机
最后我们发送两条消息,一条符合路由key,将被normal_Exchange路由到normalQueue队列中
而另一条不符合路由key,将被回退发送到备份交换机,并最终被路由到unroutedQueue队列中

在这里插入图片描述


翠花,上代码:

	public static void main(String[] args) throws
            IOException, TimeoutException, NoSuchAlgorithmException, KeyManagementException, URISyntaxException {
    
    

        // 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 主机地址: 默认为 localhost
        connectionFactory.setHost("localhost");
        // 连接端口: 默认为 5672
        connectionFactory.setPort(5672);
        // 虚拟主机名称: 默认为 /
        connectionFactory.setVirtualHost("/test");
        // 连接用户名: 默认为guest
        connectionFactory.setUsername("cfl");
        // 连接密码: 默认为guest
        connectionFactory.setPassword("cfl");
        // 从工厂获得连接:
        Connection connection = connectionFactory.newConnection();

        // 用连接创建Channel
        Channel channel = connection.createChannel();

        // 声明队列 普通交换机绑定的队列
        channel.queueDeclare("normalQueue", false, false, false, null);
        // 声明队列 alternate-exchange交换机绑定的队列
        channel.queueDeclare("unroutedQueue", false, false, false, null);

        /*
        AMQP.Exchange.DeclareOk exchangeDeclare(
            String exchange,
            BuiltinExchangeType type,
            boolean durable,
            boolean autoDelete,
            boolean internal,
            Map<String, Object> arguments) throws IOException;
        */
        // 定义参数
        Map<String, Object> arges = new HashMap<>();
        arges.put("alternate-exchange", "myAe");

        // 声明普通交换机 为DIRECT的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("normal_Exchange", BuiltinExchangeType.DIRECT, false, false, arges);
        // 声明alternate-exchange交换机 为FANOUT的交换机,交换机构造方法全部参数如上
        channel.exchangeDeclare("myAe", BuiltinExchangeType.FANOUT, false, false, null);

        /*
        Queue.BindOk queueBind(
            String queue,
            String exchange,
            String routingKey,
            Map<String, Object> arguments
        ) throws IOException;
        */
        // 队列和普通交换机绑定,绑定key为rabbit
        channel.queueBind("normalQueue", "normal_Exchange", "rabbit", null);
        // 队列和备份交换机绑定 因为交换机类型是Fanout 绑定时不需要指定绑定key
        channel.queueBind("unroutedQueue", "myAe", "", null);

        String message1 = "test with rabbit true !!!";
        String message2 = "test with rabbit false !!!";
        /*
         * 参数1:指定交换机名称
         * 参数2:指定路由键routingKey
         * 参数3:指定扩展参数暂无
         * 参数4:指定发送的消息
         */
        // 分别指定routingKey
        channel.basicPublish("normal_Exchange", "rabbit", null, message1.getBytes());
        System.out.println(" [x] Sent '" + message1 + "'");

        channel.basicPublish("normal_Exchange", "nop", null, message2.getBytes());
        System.out.println(" [x] Sent '" + message2 + "'");

        // 关闭资源
        channel.close();
        connection.close();

    }

代码跑起来,我们先看一下控制台:两条消息发送成功在这里插入图片描述
下来看一下交换机的情况: 没有问题,并且拥有备份交换机的交换机会被AE标记
在这里插入图片描述
以及我们最关心的消息的到达情况: 也是没有问题的
在这里插入图片描述
在这里插入图片描述
这里我们同样可以发现,虽然unroutedQueue队列中的消息是被备份交换机转发的,但是其消息的标注上依旧是来自normal_Exchange,也就是其源头第一个交换机,备份交换机是一个无形的存在!
最后附上总结:
在这里插入图片描述


15.示例代码地址

https://gitee.com/chenfeilin/RabbitMQ_Practice.git

猜你喜欢

转载自blog.csdn.net/cjl836735455/article/details/109901823