RabbitMQ入门笔记

第一章 安装RabbitMQ

RabbitMQ是一个消息中间件,遵循AMQP协议 (Advanced Message Queuing Protocol)。由Erlang语言开发,所以安装rabbitmq之前先安装Erlang环境。

Erlang与rabbitmq的版本必须匹配,版本匹配信息可以查看rabbit官网
官网 https://www.rabbitmq.com/which-erlang.html

在这里插入图片描述

1.1 安装Erlang

网址:https://www.erlang.org/downloads

右边是对应的版本信息,中间是选择下载的系统环境。
在这里插入图片描述
下载成功,点击安装,配置环境。
打开电脑的环境配置,变量值是erlang的安装路径。
在这里插入图片描述

修改Path的值,点击新建 复制 %ERLANG_HOME%\bin
然后保存,这里的作用是为了在cmd窗口能够执行erlang的命令。
在这里插入图片描述
测试,win + r 打开cmd窗口,输入 erl -v
出现版本号表示安装成功。
在这里插入图片描述

1.2 安装 RabbitMQ

下载地址 https://www.rabbitmq.com/install-windows.html

在这里插入图片描述
往下滑,选择下载,这个下载资源来自GitHub,比较慢,可以找其他渠道下载。
在这里插入图片描述
下载安装成功之后,进入安装目录。

在这里插入图片描述
点击上方的目录,输入cmd 加一个空格 然后回车。
在这里插入图片描述
执行如下命令,安装管理插件 rabbitmq-plugins enable rabbitmq_management
在这里插入图片描述
执行命令 查看安装状态 rabbitmqctl status
在这里插入图片描述
点击运行脚本,运行服务器。在这里插入图片描述

访问 后台管理地址 http://localhost:15672/#/
登录账号与密码都是 guest
在这里插入图片描述
登录成功如下
在这里插入图片描述
到此安装rabbitmq安装成功。

1.3 RabbitMQ后台管理系统

1.3.1 概述

由于当前没有进行任何配置,所以有效内容没有数据。
在这里插入图片描述
Totals:显示消息在单位时间内的数据情况。
Node:显示电脑的磁盘、内存等信息,选择表单的最右边加减号图标即可增加或者减少要识别的内容。
在这里插入图片描述
对连接,信道,队列流失统计
在这里插入图片描述
端口与上下文信息
在这里插入图片描述
配置文件导入与导出。
在这里插入图片描述

1.3.2 连接

信息的生产者与消费者的连接状态都在这里,由于没有创建,所以,没有任何信息。
在这里插入图片描述

1.3.3 信道

在这里插入图片描述

1.3.4 交换机

在这里插入图片描述

1.3.4 队列

在这里插入图片描述

1.3.4 admin

用于添加用户与虚拟主机。
在这里插入图片描述

1.4 配置用户与虚拟主机

为什么要配置用户与虚拟主机呢?
如果你是一个人开发,就你一个人那就没有问题,如果有很多人开发,他们都要查看rabbitmq时,只有一个账号是不行的,所以,就要配置一个用户,那虚拟机呢,主要是隔离每一个队列的配置。如,一个用户把自己的所有消息服务配置在一个虚拟主机当中,很容易管理各个的配置信息。

1.4.1 配置用户

如果不使用guest,我们也可以自己创建一个用户
1、 超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
2、 监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
3、 策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息。
4、 普通管理者(management)
仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
5、 其他
无法登陆管理控制台,通常就是普通的生产者和消费者。

在这里插入图片描述

添加成功如下,点击用户名配置权限。
在这里插入图片描述
在这里插入图片描述

1.4.2 新增虚拟主机

主机的名称必须是 /开头
在这里插入图片描述
添加成功如下
在这里插入图片描述
设置虚拟主机的访问权限,如哪些用户能访问这个虚拟主机。
在这里插入图片描述
配置好之后,退出登录,使用刚刚配置好的用户登录虚拟主机。
退出登录

在这里插入图片描述
查看地址栏并登录,显示登录成功。
在这里插入图片描述

第二章 RabbitMQ 配置

2.1 rabbitmq 架构

组成部分说明:

  • Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue
  • Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
  • Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的
  • Producer:消息生产者,即生产方客户端,生产方客户端将消息发送
  • Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。

在这里插入图片描述
生产者发送消息流程:
1、生产者和Broker建立TCP连接。
2、生产者和Broker建立通道。
3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
4、Exchange将消息转发到指定的Queue(队列)
消费者接收消息流程:
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者。
5、消费者接收到消息。
6、ack回复

点击Tutorials进入官网教程
在这里插入图片描述
进入之后,有7中消息模式,下面的语言是对应的案例实现。
在这里插入图片描述
在这里插入图片描述

2.2 web后台创建消息队列

2.2.1 基本消息模式 (交换机类型:direct)

这种模式没有交换机,直接生产消息,直接消费。
在这里插入图片描述

在这里插入图片描述
创建成功,如果没有出现队列,则查看当前页面的虚拟主机属性
在这里插入图片描述
配置队列,点击队列名称。
在这里插入图片描述
发送消息,其他默认
在这里插入图片描述
发布成功后,概述出现一条消息
在这里插入图片描述
获取消息
在这里插入图片描述
查看概述,消息已被消费
在这里插入图片描述

2.2.2 work queue 消息模式

work模式属于第一种的模式升级版,可以多个消费队列去消费生产者发布的消息,而且消息只能被一个消费者消费,消费者队列属于竞争关系,该模式需要用代码实现,web后台管理暂时不实现。
在这里插入图片描述

2.2.3 Publish/subscribe 模式(交换机类型:Fanout)

在这里插入图片描述
发现虚拟主机下已经默认配置了常用类型的交换机,我们就不创建了,直接点击交换机名称,进入交换机配置。
进入 amq.direct交换机
在这里插入图片描述

在这里插入图片描述
绑定队列
在这里插入图片描述
通过交换机发送消息到queue-01
在这里插入图片描述
进入queue-01 队列获取消息
在这里插入图片描述
消息获取成功。

在这里插入图片描述

2.2.4 Routing 路由模型(交换机类型:direct)

该模式下,所有的队列的key是确定的,只有指定的key与消费者队列的key相匹配,消息才能被消费。
在这里插入图片描述

routing路由模式,只要指定的路由key匹配,该队列才会收到消息。
创建两个队列。
在这里插入图片描述
分别设置rout-queue-01 rout-queue-02的队列key,设置的这个队列key会与交换机的路由key相匹配,如果匹配成功,则对应的队列消费该消息。
在这里插入图片描述
进入交换机查看
在这里插入图片描述

发送消息
在这里插入图片描述
查看key所在的队列,是否有该消息。
点击rout-queue-01队列查看。
在这里插入图片描述
获取消息。
在这里插入图片描述

2.2.5 Topics 通配符模式(交换机类型:topics)

该模式是上个模式的升级版,可以对路由key进行通配符操作,满足通配符匹配的队列才能消费消息。
在这里插入图片描述
Routingkey一般都是有一个或者多个单词组成,多个单词之间以“.”分割,例如:inform.sms
通配符规则:

#:匹配一个或多个词

*:匹配不多不少恰好1个词

案例:

AA.# : AA.AA AA.AA.BB
#.AA : AA.AA AA.BB.AA
#.AA.# : CC.BB.AA.BB.CC

BB.* : BB.B
*.BB : AA.BB

结合

.#.A..# : B.B.B.A.B.C.C

创建队列

在这里插入图片描述
配置每一个队列的路由key
在这里插入图片描述
配置完成后,进入交换机查看 amq.top 交换机查看绑定的数据。
在这里插入图片描述
发送消息,这个消息的key能发送到三个topic队列。
在这里插入图片描述
三个队列全部收到消息
在这里插入图片描述
进入队列查看消息。
在这里插入图片描述
发送一个消息只有topic-01 能收到。
topic-01.topic.topic-01.topic-01 将匹配到 *.topic.#
在这里插入图片描述
发送成功如下
在这里插入图片描述
消息内容
在这里插入图片描述

总结

通过web后台管理系统,实现了4种模式,分别是基本消息模式、发布与订阅模式、路由模式,主题模式,还剩三种,留到代码中去实践,先分析已经实践过的4中。
第一种基本消息模式:不需要交换机,直接是点对点的发布信息与消费消息(默认是有交换机的,只不过不需要我们配置)。

第二种发布与订阅模式:需要用到交换机,还有配置消费队列与指定的交换机相匹配,生产者通过交换机把消息发送到指定的消费队列中。

第三种Routing模式:需要用到交换机,需要配置消费队列与交换机绑定,还有设置交换机路由,交换机路由负责跟所有绑定了交换机的消费队列进行路由匹配,只有路由匹配成功的,才能消费到消息。

第四种Topic模式:需要用到交换机,需要配置消费队列与交换机绑定,还有设置交换机路由,交换机的路由配置改成了通配符,使用更加灵活。

剩余 work queue、 rpc远程调用、 Publisher Confirms模式在代码中说明。

第三章 代码配置

3.1 创建项目

创建一个maven项目

        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.7.1</version>
        </dependency>
 <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.14.0</version>
        </dependency>

定义一个连接rabbitmq的工具类

public class ConnectionUtil {
    /**
     * 建立与RabbitMQ的连接
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址 如果使用的guest账号,则这里要设置成localhost,或者 127.0.0.1
        factory.setHost("127.0.0.1");
        //端口 默认的端口,不能修改
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的mq
        factory.setVirtualHost("/mq");
        factory.setUsername("mqtest");
        factory.setPassword("123456");
        // 通过工厂获取连接
        return factory.newConnection();
    }
}

3.2 基本消息模式

在这里插入图片描述
P:生产者,也就是要发送消息的程序

C:消费者:消息的接受者,会一直等待消息到来。

queue:消息队列,图中红色部分。可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。

生产者

public class Producer {

        private final static String QUEUE_NAME = "java_queue";

        public static void main(String[] argv) throws Exception {
            // 1、获取到连接
            Connection connection = ConnectionUtil.getConnection();
            // 2、从连接中创建信道,使用通道才能完成消息相关的操作
            Channel channel = connection.createChannel();
            // 3、声明(创建)队列
            //参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
            /**
             * 参数明细
             * 1、queue 队列名称
             * 2、durable 是否持久化,如果持久化,mq重启后队列还在
             * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
             * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
             * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            // 4、消息内容
            String message = "Hello RabbitMQ!";
            // 向指定的队列中发送消息
            //参数:String exchange, String routingKey, BasicProperties props, byte[] body
            /**
             * 参数明细:
             * 1、exchange,交换机,如果不指定将使用mq的默认交换机(设置为"")
             * 2、routingKey,路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,routingKey设置为队列的名称
             * 3、props,消息的属性
             * 4、body,消息内容
             */
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");

            //关闭通道和连接(资源关闭最好用try-catch-finally语句处理)
//            channel.close();
//            connection.close();
        }

}

代码后面我们没有关闭工厂与信道连接,所以,在web后台管理能看到连接信息,及队列信息。
在这里插入图片描述
在这里插入图片描述
消费者

public class Consumer {

        private final static String QUEUE_NAME = "java_queue";

        public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();
            //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用

                /**
                 * 当接收到消息后此方法将被调用
                 *
                 * @param consumerTag 消费者标签,用来标识消费者的,在监听队列时设置channel.basicConsume
                 * @param envelope    信封,通过envelope
                 * @param properties  消息属性
                 * @param body        消息内容
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //交换机
                    String exchange = envelope.getExchange();
//                    System.out.println("交换机:"+exchange);
                    //消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
//                    System.out.println("信息id:"+deliveryTag);
                    // body 即消息体
                    String msg = new String(body, "utf-8");
                    System.out.println(" [x] received : " + msg + "!");
                }
            };

            // 监听队列,第二个参数:是否自动进行消息确认。
            //参数:String queue, boolean autoAck, Consumer callback
            /**
             * 参数明细:
             * 1、queue 队列名称
             * 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
             * 3、callback,消费方法,当消费者接收到消息要执行的方法
             */
            channel.basicConsume(QUEUE_NAME,true,consumer);


            //关闭信道连接
           // channel.close();
            //关闭连接
            //connection.close();
        }
}

消息生产者与消费者已创建完成,显运行生产者生产消息,在运行消费者消费消息。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.3 消息确认机制(ACK)

自动ACK:消息一旦被接收,消费者自动发送ACK
手动ACK:消息接收后,不会发送ACK,需要手动调用

两者的区别:
自动ACK机制:只要消费端收到消息,就回复消息被消费,然后删除该消息,出现异常,或者出现其他原因,只要消费者收到就回复确认,不管这个消息有没有处理完成。

手动ACK:需要手动确认消息已经被消费,如果消息消费了但是没有回复,消息会一直存在队列中,必须手动的回复才会消失在队列中。

消费者该为手动确认。

public class Consumer {

        private final static String QUEUE_NAME = "java_queue";

        public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();
            //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用

                /**
                 * 当接收到消息后此方法将被调用
                 *
                 * @param consumerTag 消费者标签,用来标识消费者的,在监听队列时设置channel.basicConsume
                 * @param envelope    信封,通过envelope
                 * @param properties  消息属性
                 * @param body        消息内容
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //交换机
                    String exchange = envelope.getExchange();
//                    System.out.println("交换机:"+exchange);
                    //消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
                    System.out.println("信息id:"+deliveryTag);
                    // body 即消息体
                    String msg = new String(body, "utf-8");
                    System.out.println(" [x] received : " + msg + "!");

                    /*
                     *  void basicAck(long deliveryTag, boolean multiple) throws IOException;
                     *  deliveryTag:用来标识消息的id
                     *  multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(),true);
                }
            };

            // 监听队列,第二个参数:是否自动进行消息确认。
            //参数:String queue, boolean autoAck, Consumer callback
            /**
             * 参数明细:
             * 1、queue 队列名称
             * 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
             * 3、callback,消费方法,当消费者接收到消息要执行的方法
             */
            channel.basicConsume(QUEUE_NAME,false,consumer);

            //关闭信道连接
           // channel.close();
            //关闭连接
            //connection.close();
        }
}

3.4 work queue模式

work queues与基本消息模式相比,多了一个消费端,两个消费端共同消费同一个队列中的消息,但是一个消息只能被一个消费者获取。
接下来我们来模拟这个流程:
P:生产者:任务的发布者
C1:消费者1:领取任务并且完成任务,假设完成速度较慢(模拟耗时)
C2:消费者2:领取任务并且完成任务,假设完成速度较快

通过 BasicQos 方法设置prefetchCount = 1。这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理1个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。相反,它会将其分派给不是仍然忙碌的下一个Consumer。

值得注意的是:prefetchCount在手动ack的情况下才生效,自动ack不生效。

生产者

public class ProducerWork {

        private final static String QUEUE_NAME = "work_queue";

        public static void main(String[] argv) throws Exception {
            // 1、获取到连接
            Connection connection = ConnectionUtil.getConnection();
            // 2、从连接中创建信道,使用通道才能完成消息相关的操作
            Channel channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
           for (int i=0;i<30;i++){
               // 4、消息内容
               String message = "Hello RabbitMQ:"+i;
               channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
               System.out.println(" ["+i+"] Sent '" + message + "'");
               Thread.sleep(i * 2);
           }
           channel.close();
            connection.close();
        }
}

消费者01

public class ConsumerWork01 {

        private final static String QUEUE_NAME = "work_queue";
        public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();

            channel.basicQos(1);
            //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "utf-8");
                    System.out.println(" [x] received : " + msg + "!");
                    channel.basicAck(envelope.getDeliveryTag(),true);
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            channel.basicConsume(QUEUE_NAME,false,consumer);

            //关闭信道连接
           // channel.close();
            //关闭连接
            //connection.close();
        }
}

消费者02

public class ConsumerWork02 {

        private final static String QUEUE_NAME = "work_queue";
        public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();

            //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "utf-8");
                    System.out.println(" [x] received : " + msg + "!");
                    channel.basicAck(envelope.getDeliveryTag(),true);

                }
            };
            channel.basicConsume(QUEUE_NAME,false,consumer);

            //关闭信道连接
           // channel.close();
            //关闭连接
            //connection.close();
        }
}

在这里插入图片描述
在这里插入图片描述
总结:如果一个消费者消费消息很慢,则在它的队列中加入 channel.basicQos(1);

3.5 订阅模式分类

订阅模型分类
1、一个生产者多个消费者
2、每个消费者都有一个自己的队列
3、生产者没有将消息直接发送给队列,而是发送给exchange(交换机、转发器)
4、每个队列都需要绑定到交换机上
5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者消费

Exchange类型有以下几种:

Fanout:广播,将消息交给所有绑定到交换机的队列

Direct:定向,把消息交给符合指定routing key 的队列

Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

Header:header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配队列。

3.5.1 Publish/subscribe

(交换机类型:Fanout,也称为广播 )
在这里插入图片描述

生产者
创建交换机 通过交换机把消息发送到队列中


public class ProducerPublish {

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

        public static void main(String[] argv) throws Exception {
            // 1、获取到连接
            Connection connection = ConnectionUtil.getConnection();
            // 2、从连接中创建信道,使用通道才能完成消息相关的操作
            Channel channel = connection.createChannel();
            // 声明一个交换机,类型为FANOUT
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
               // 4、消息内容
               String message = "Hello RabbitMQ:";
               //EXCHANGE_NAME:交换机名称, null:路由key,null:属性, message:消息体
               channel.basicPublish( EXCHANGE_NAME, "",null, message.getBytes());
               System.out.println(" Sent '" + message + "'");
           channel.close();
            connection.close();
        }
}

消费者01
消费交换机发来的消息

public class ConsumerPublish01 {
        //队列
        private final static String QUEUE_NAME = "publish01_queue";
        //交换机
    private final static String EXCHANGE_NAME = "publish_exchange";
    public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            // 绑定到交换机
            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
            //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "utf-8");
                    System.out.println("received : " + msg + "!");
                }
            };
            channel.basicConsume(QUEUE_NAME,true,consumer);
        }
}

消费者02
消费交换机发来的消息

public class ConsumerPublish02 {
        //队列
        private final static String QUEUE_NAME = "publish02_queue";
        //交换机
    private final static String EXCHANGE_NAME = "publish_exchange";
    public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();

            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            // 绑定到交换机
            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
            //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "utf-8");
                    System.out.println("received : " + msg + "!");
                }
            };
            channel.basicConsume(QUEUE_NAME,true,consumer);
        }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1、publish/subscribe与work queues有什么区别。

区别:
1)work queues不用定义交换机,而publish/subscribe需要定义交换机。
2)publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。
3)publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实际上work queues会将队列绑定到默认的交换机 。
相同点:
所以两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。
2、实际工作用 publish/subscribe还是work queues。
建议使用 publish/subscribe,发布订阅模式比工作队列模式更强大(也可以做到同一队列竞争),并且发布订阅模式可以指定自己专用的交换机。

3.5.2 Routing

路由模式:表示交换机的路由key与队列的路由key一致,才会消费到消息。
在这里插入图片描述
生产者

public class ProducerRouting {

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

        public static void main(String[] argv) throws Exception {
            // 1、获取到连接
            Connection connection = ConnectionUtil.getConnection();
            // 2、从连接中创建信道,使用通道才能完成消息相关的操作
            Channel channel = connection.createChannel();
            // 声明一个交换机,类型为FANOUT
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
               // 4、消息内容
               String message = "Hello routing01:";
               //发送到 routing02队列
                String message02 = "Hello routing02:";

            //EXCHANGE_NAME:交换机名称, null:路由key,null:属性, message:消息体
               channel.basicPublish( EXCHANGE_NAME, "routing01",null, message.getBytes());
            channel.basicPublish( EXCHANGE_NAME, "routing02",null, message02.getBytes());
            System.out.println(" Send '" + message + "'");
            System.out.println(" Send '" + message02 + "'");
            channel.close();
            connection.close();
        }
}

消费者01

public class ConsumerRouting01 {
        //队列
        private final static String QUEUE_NAME = "routing01_queue";
        //交换机
    private final static String EXCHANGE_NAME = "routing_exchange";
    public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            // 绑定到交换机 String queue, String exchange, String routingKey
            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"routing01");
            //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "utf-8");
                    System.out.println("received : " + msg + "!");
                }
            };
            channel.basicConsume(QUEUE_NAME,true,consumer);
        }
}

消费者02

public class ConsumerRouting02 {
        //队列
        private final static String QUEUE_NAME = "routing02_queue";
        //交换机
    private final static String EXCHANGE_NAME = "routing_exchange";
    public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            // 绑定到交换机 String queue, String exchange, String routingKey
            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"routing02");
            //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "utf-8");
                    System.out.println("received : " + msg + "!");
                }
            };
            channel.basicConsume(QUEUE_NAME,true,consumer);
        }
}

3.5.3 Topics

通配符模式(交换机类型:topics)
Routingkey一般都是有一个或者多个单词组成,多个单词之间以“.”分割,例如:inform.sms
通配符规则:

#:匹配一个或多个词

*:匹配不多不少恰好1个词

案例:

AA.# : AA.AA AA.AA.BB
#.AA : AA.AA AA.BB.AA
#.AA.# : CC.BB.AA.BB.CC

BB.* : BB.B
*.BB : AA.BB

结合

.#.A..# : B.B.B.A.B.C.C

生产者

public class ProducerTopic {

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

        public static void main(String[] argv) throws Exception {
            // 1、获取到连接
            Connection connection = ConnectionUtil.getConnection();
            // 2、从连接中创建信道,使用通道才能完成消息相关的操作
            Channel channel = connection.createChannel();
            // 声明一个交换机,类型为FANOUT
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
               // 4、消息内容
               String message = "Hello topic01:";
               //发送到 routing02队列
                String message02 = "Hello topic02:";

            //EXCHANGE_NAME:交换机名称, null:路由key,null:属性, message:消息体
               channel.basicPublish( EXCHANGE_NAME, "topic.topic.01",null, message.getBytes());
            channel.basicPublish( EXCHANGE_NAME, "01-topic.topic",null, message02.getBytes());
            channel.basicPublish( EXCHANGE_NAME, "02-topic.topic",null, message.getBytes());
            channel.basicPublish( EXCHANGE_NAME, "02-topic.topic.02",null, message02.getBytes());

            System.out.println(" Send '" + message + "'");
            System.out.println(" Send '" + message02 + "'");
            channel.close();
            connection.close();
        }
}

消费者01

public class ConsumerTopic01 {
        //队列
        private final static String QUEUE_NAME = "topic01_queue";
        //交换机
    private final static String EXCHANGE_NAME = "topic_exchange";
    public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            // 绑定到交换机 String queue, String exchange, String routingKey
            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"#.topic.*");

             channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"#.topic");

        //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "utf-8");
                    System.out.println("received : " + msg + "!");
                }
            };
            channel.basicConsume(QUEUE_NAME,true,consumer);
        }
}

消费者02

public class ConsumerTopic02 {
        //队列
        private final static String QUEUE_NAME = "topic02_queue";
        //交换机
    private final static String EXCHANGE_NAME = "topic_exchange";
    public static void main(String[] argv) throws Exception {
            // 获取到连接
            Connection connection = ConnectionUtil.getConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
            Channel channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            // 绑定到交换机 String queue, String exchange, String routingKey
            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"#.topic.*");

             channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"#.topic.#");

        //实现消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "utf-8");
                    System.out.println("received : " + msg + "!");
                }
            };
            channel.basicConsume(QUEUE_NAME,true,consumer);
        }
}

在这里插入图片描述

3.5.3 RPC (远程调用)

RPC模型示意图:
在这里插入图片描述
基本概念:

Callback queue 回调队列,客户端向服务器发送请求,服务器端处理请求后,将其处理结果保存在一个存储体中。而客户端为了获得处理结果,那么客户在向服务器发送请求时,同时发送一个回调队列地址reply_to。

Correlation id 关联标识,客户端可能会发送多个请求给服务器,当服务器处理完后,客户端无法辨别在回调队列中的响应具体和那个请求时对应的。为了处理这种情况,客户端在发送每个请求时,同时会附带一个独有correlation_id属性,这样客户端在回调队列中根据correlation_id字段的值就可以分辨此响应属于哪个请求。

流程说明:

当客户端启动的时候,它创建一个匿名独享的回调队列。
在 RPC 请求中,客户端发送带有两个属性的消息:一个是设置回调队列的 reply_to 属性,另一个是设置唯一值的 correlation_id 属性。
将请求发送到一个 rpc_queue 队列中。
服务器等待请求发送到这个队列中来。当请求出现的时候,它执行他的工作并且将带有执行结果的消息发送给 reply_to 字段指定的队列。
客户端等待回调队列里的数据。当有消息出现的时候,它会检查 correlation_id 属性。如果此属性的值与请求匹配,将它返回给应用。

3.6 持久化

3.6.1 队列持久化

队列持久化:在信道中声明队列是对durable 设置为true。

/**
             * 参数明细
             * 1、queue 队列名称
             * 2、durable 是否持久化,如果持久化,mq重启后队列还在
             * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
             * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
             * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
             */
 channel.queueDeclare(QUEUE_NAME, true, false, false, null);

3.6.2 交换机持久化

durable:设置为true,表示持久化。

exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable)
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT,true);

3.6.3 消息持久化

在发布消息的方法中声明一个MessageProperties.PERSISTENT_TEXT_PLAIN 的参数就能实现持久化。

channel.basicPublish( EXCHANGE_NAME, "", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

3.7 死信队列

消息成为死信的三种情况:

  • 原队列存在消息过期设置,消息到达超时时间未被消费;
  • 队列消息长度到达限制;
  • 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;

在这里插入图片描述

3.7.1 超过过期时间

正常生产者(正常交换机)

public class ProducerNormal {

    private  final static String EXCHANGE_NORMAL = "normal_Exchange";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        //创建正常交换机
        channel.exchangeDeclare(EXCHANGE_NORMAL, BuiltinExchangeType.TOPIC);
        //        设置消息过期时间
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
        //创建正常消息
        for (int i = 0 ;i < 10; i++) {
            String normalMessage = "send new"+i+" message to normal_exchange!";
            channel.basicPublish(EXCHANGE_NORMAL, "normal.topic", properties, normalMessage.getBytes());
            System.out.println("message:"+normalMessage);
        }
        channel.close();
        connection.close();
    }

}

正常消费者(正常队列)


public class ConsumerNormal {

    private  final static String EXCHANGE_NORMAL = "normal_Exchange";
    private final static String  NORMAL_QUEUE = "normal_queue";
    private  final static String EXCHANGE_DEAL = "deal_Exchange";
    private final static String  DEAL_QUEUE = "deal_queue";

    public static void main(String[] args) {
        try {
            Connection connection = ConnectionUtil.getConnection();
             Channel channel = connection.createChannel();
             // 声明死信交换机
             channel.exchangeDeclare(EXCHANGE_DEAL, BuiltinExchangeType.TOPIC);
             //声明正常交换机
           channel.exchangeDeclare(EXCHANGE_NORMAL, BuiltinExchangeType.TOPIC);

             // 创建死信队列
            channel.queueDeclare(DEAL_QUEUE,false,false,false,null);
            // 死信队列与死信交换机绑定
            channel.queueBind(DEAL_QUEUE,EXCHANGE_DEAL,"*.deal_topic"); 
            //正常队列绑定死信队列信息
            Map<String, Object> params = new HashMap<>();
            //正常队列设置死信交换机 参数 key 是固定值
            params.put("x-dead-letter-exchange", EXCHANGE_DEAL);
            //正常队列设置死信 routing-key 参数 key 是固定值
            params.put("x-dead-letter-routing-key", "deal.deal_topic");
			// 声明正常队列
             channel.queueDeclare(NORMAL_QUEUE,false,false,false,params);
             //正常队列与正常交换机绑定
             channel.queueBind(NORMAL_QUEUE,EXCHANGE_NORMAL,"*.topic");
            channel.basicConsume(NORMAL_QUEUE,true,new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                             String message = new String(body, StandardCharsets.UTF_8);
                    System.out.println("from normal_exchange"+message);
                }
            });
        }catch (Exception e){
            System.out.println(e.getMessage());
        }


    }
}

死信消费者(死信队列)

public class ConsumerDeal {
    private final static String  DEAL_QUEUE = "deal_queue";
    public static void main(String[] args) {
        try {
            Connection connection = ConnectionUtil.getConnection();
             Channel channel = connection.createChannel();
            channel.basicConsume(DEAL_QUEUE,true,new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                             String message = new String(body, StandardCharsets.UTF_8);
                    System.out.println("from deal_exchange"+message);
                }
            });
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}

测试
1 查看队列里面没有消息;再运行正常消费者创建交换机与队列。
在这里插入图片描述
2 运行生产者,发现死信队列有10条消息,这是因为我们没有运行正常的消费者队列,时间超过了设置的过期时间,消息就全部发送到死信队列了。
在这里插入图片描述3 运行正常消费者;发现消息还在死信队列中。
4 运行死信队列,消息被消费。
在这里插入图片描述
在这里插入图片描述
测试 :运行正常消费者队列,再运行正常生产者;发现消息被正常消费者队列消费。

3.7.2 队列达到最大长度

在正常消费者(正常队列)中加入最大消息数

  params.put("x-max-length",6);

3.7.3 消费者拒绝消费消息

删除队列
在这里插入图片描述
再运行消费者队列。这样做的原因是:如果不删除,队列名称一样,加了参数跟注释了参数不会同步到队列上。
修改正常消费者队列,把自动确认消息该为手动。



            channel.basicConsume(NORMAL_QUEUE,false,new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                             String message = new String(body, StandardCharsets.UTF_8);
                             //
                             channel.basicReject(envelope.getDeliveryTag(),false);
                             // 确认消息
                             channel.basicAck(envelope.getDeliveryTag(),true);
                  //  System.out.println("from normal_exchange"+message);
                }
            });

3.8 TTL队列

time to live,即生存时间

RabbitMQ 支持消息的过期时间,可以在发消息是指定

RabbitMQ 支持队列的过期时间,从消息入队开始计算,只要超过了队列的超时时间配置,那么消息会自动清除。

public class TTLDueue{

    /**
     * ttl队列/消息
     */
        public static void main(String[] args) throws Exception{
            Connection connection = ConnectionUtil.getConnection();
            Channel channel = connection.createChannel();
            String exchangeName = "test_ttl_exchange";
            String routingKey = "ttl.abc";
            String queueName = "test_ttl_queue";

            // 声明exchange
            channel.exchangeDeclare(exchangeName, "topic");
            // 声明 queue
            channel.queueDeclare(queueName, true, false, false, null);

            Map<String, Object> arguments = new HashMap<>();
            // 队列ttl,设置为8s
            arguments.put("message-ttl", 8000);
            channel.queueBind(queueName, exchangeName, routingKey, arguments);

            for (int i = 0; i < 3; i++) {
                // expiration("10000") 设置消息8s过期,消息的ttl
                AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().deliveryMode(2).contentEncoding("UTF-8")
                        .expiration("8000").build();

                String msg = "这是第" + i + "条ack消息";
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                channel.basicPublish(exchangeName, routingKey, false, props, msg.getBytes(StandardCharsets.UTF_8));
            }

            channel.close();
            connection.close();
        }

    }

3.9 延迟队列

延迟队列:
所谓 “延时消息” 是指当消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
在RabbitMQ中并没有支持延时队列的功能,延时队列可以通过 【过期时间 + 死信队列】 来实现

代码实现:思路是把死信队列中的消费消息的代码去掉。

public class ConsumerNormal {

    private  final static String EXCHANGE_NORMAL = "normal_Exchange";
    private final static String  NORMAL_QUEUE = "normal_queue";
    private  final static String EXCHANGE_DEAL = "deal_Exchange";
    private final static String  DEAL_QUEUE = "deal_queue";

    public static void main(String[] args) {
        try {
            Connection connection = ConnectionUtil.getConnection();
             Channel channel = connection.createChannel();
            channel.exchangeDeclare(EXCHANGE_DEAL, BuiltinExchangeType.TOPIC);
            channel.exchangeDeclare(EXCHANGE_NORMAL, BuiltinExchangeType.TOPIC);

            channel.queueDeclare(DEAL_QUEUE,false,false,false,null);
            channel.queueBind(DEAL_QUEUE,EXCHANGE_DEAL,"*.deal_topic");            //正常队列绑定死信队列信息
            Map<String, Object> params = new HashMap<>();
            //正常队列设置死信交换机 参数 key 是固定值
            params.put("x-dead-letter-exchange", EXCHANGE_DEAL);
            //正常队列设置死信 routing-key 参数 key 是固定值
            params.put("x-dead-letter-routing-key", "deal.deal_topic");
         //   params.put("x-max-length",6);
             channel.queueDeclare(NORMAL_QUEUE,false,false,false,params);
             channel.queueBind(NORMAL_QUEUE,EXCHANGE_NORMAL,"#.topic");


//            channel.basicConsume(NORMAL_QUEUE,false,new DefaultConsumer(channel){
//                @Override
//                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//                             String message = new String(body, StandardCharsets.UTF_8);
//                             //
//                             channel.basicReject(envelope.getDeliveryTag(),false);
//                             // 确认消息
                             channel.basicAck(envelope.getDeliveryTag(),true);
//                  //  System.out.println("from normal_exchange"+message);
//                }
//            });
        }catch (Exception e){
            System.out.println(e.getMessage());
        }


    }
}

第四章 与spring整合

1 导入依赖

    <dependencies>     
<!--        与spring整合-->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-amqp</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        
<!--        日志-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.14.0</version>
        </dependency>
    </dependencies>

4.1 topic模式整合

外部属性

rabbit.hosts=127.0.0.1
rabbit.username=mqtest
rabbit.password=123456
rabbit.port=5672
rabbit.virtualHost=/mq

rabbit.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/rabbit
                        http://www.springframework.org/schema/rabbit/spring-rabbit-1.7.xsd">

    <context:property-placeholder location="rabbitmqdata.properties"/>
    <bean id="rabbitConnectionFactory"
          class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
        <constructor-arg value="${rabbit.hosts}"/>
        <property name="username" value="${rabbit.username}"/>
        <property name="password" value="${rabbit.password}"/>
        <property name="port" value="${rabbit.port}"/>
        <property name="virtualHost" value="${rabbit.virtualHost}"/>
    </bean>


    <rabbit:admin connection-factory="rabbitConnectionFactory"/>

    <!-- autoDelete:是否自动删除 durable:持久化  -->
    <rabbit:queue name="pro_topic_queue" durable="false"/>
    <rabbit:queue name="con_topic_queue" durable="false"/>
    <!-- topic主题 -->
    <rabbit:topic-exchange id="topicExchange" name="topic.exchange" durable="false">
        <rabbit:bindings>
            <rabbit:binding queue="con_topic_queue" pattern="*.topic" />
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!-- 创建rabbitTemplate 消息模板类 -->
    <rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory"/>

    <bean name="queueListenter" class="com.example.mq.spring.TopicConsumer"/>
    <rabbit:listener-container>
<!--        消费者监听对象绑定消费者队列,如果消费者队列里面有消息就消费-->
    <rabbit:listener ref="queueListenter" queue-names="con_topic_queue"/>

    </rabbit:listener-container>

</beans>

生产者

public class TopicProducer {

    public static void main(String[] args){
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("rabbit.xml");
        RabbitTemplate template = context.getBean(RabbitTemplate.class);
        // 设置构建消息属性
        MessagePropertiesBuilder builder = MessagePropertiesBuilder.newInstance();
        builder.setContentEncoding("utf-8"); // 设置内容响应编码
        builder.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);  // 设置内容响应格式
        
        try {
            // 生成Message对象
            Message message = MessageBuilder.withBody("你好,世界".getBytes("UTF-8")).andProperties(builder.build()).build();
            template.send("topic.exchange", "rabbit.topic",message);
            System.out.println("消息发送成功!" + message);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

消费者

public class TopicConsumer implements MessageListener {

    @Override
    public void onMessage(Message message) {
        System.out.print("receive msg: "+new String(message.getBody(), StandardCharsets.UTF_8));
    }
}

在这里插入图片描述

第五章 与springboot整合

5.1 topic模式整合

pom


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


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

配置


# 应用名称
spring.application.name=demo
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=mqtest
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/mq
#每次从队列中取一个,轮询分发,默认是公平分发
spring.rabbitmq.listener.simple.prefetch=1
#失败后重试
spring.rabbitmq.listener.simple.default-requeue-rejected=true

生产者

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TopicConfig {
    public static final String QUEUE_EMAIL = "queue_email";//email队列
    public static final String QUEUE_SMS = "queue_sms";//sms队列
    public static final String EXCHANGE_NAME="topic.exchange";//topics类型交换机
    public static final String ROUTINGKEY_EMAIL="topic.#.email.#";
    public static final String ROUTINGKEY_SMS="topic.#.sms.#";

    //声明交换机
    @Bean(EXCHANGE_NAME)
    public Exchange exchange(){
        //durable(true) 持久化,mq重启之后交换机还在
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    //声明email队列
    /*
     *   new Queue(QUEUE_EMAIL,true,false,false)
     *   durable="true" 持久化 rabbitmq重启的时候不需要创建新的队列
     *   auto-delete 表示消息队列没有在使用时将被自动删除 默认是false
     *   exclusive  表示该消息队列是否只在当前connection生效,默认是false
     */
    @Bean(QUEUE_EMAIL)
    public Queue emailQueue(){
        return new Queue(QUEUE_EMAIL);
    }
    //声明sms队列
    @Bean(QUEUE_SMS)
    public Queue smsQueue(){
        return new Queue(QUEUE_SMS);
    }

    //ROUTINGKEY_EMAIL队列绑定交换机,指定routingKey
    @Bean
    public Binding bindingEmail(@Qualifier(QUEUE_EMAIL) Queue queue,
                                @Qualifier(EXCHANGE_NAME) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_EMAIL).noargs();
    }
    //ROUTINGKEY_SMS队列绑定交换机,指定routingKey
    @Bean
    public Binding bindingSMS(@Qualifier(QUEUE_SMS) Queue queue,
                              @Qualifier(EXCHANGE_NAME) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_SMS).noargs();
    }

}

发布消息

@SpringBootTest
@RunWith(SpringRunner.class)

class DemoApplicationTests {

    @Test
    void contextLoads() {
    }
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    public void sendMsgByTopics() {

        /**
         * 参数:
         * 1、交换机名称
         * 2、routingKey
         * 3、消息内容
         */
        for (int i = 0; i < 5; i++) {
            String message = "恭喜您,注册成功!userid=" + i;
            rabbitTemplate.convertAndSend(TopicConfig.EXCHANGE_NAME, "topic.sms.email", message);
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

运行测试方法,查看web后台
在这里插入图片描述
在这里插入图片描述

消费者

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class Consumer {

    //监听邮件队列
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "queue_email", durable = "true"),
            exchange = @Exchange(
                    value = "topic.exchange",
                    ignoreDeclarationExceptions = "true",
                    type = ExchangeTypes.TOPIC
            ),
            key = {"topic.#.email.#","email.*"}))
    public void rece_email(String msg){
        System.out.println(" [邮件服务] received : " + msg + "!");
    }

    //监听短信队列
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "queue_sms", durable = "true"),
            exchange = @Exchange(
                    value = "topic.exchange",
                    ignoreDeclarationExceptions = "true",
                    type = ExchangeTypes.TOPIC
            ),
            key = {"topic.#.sms.#"}))
    public void rece_sms(String msg){
        System.out.println(" [短信服务] received : " + msg + "!");
    }
}

再次运行测试,消息被消费。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值