当前位置:   article > 正文

springCloud学习【4】之RabbitMQ_springcloud rabbitmq

springcloud rabbitmq

一 初始MQ

1.1 同步和异步通讯

  • 微服务间通讯有同步和异步两种方式:
    • 同步通讯:需要实时响应【像打电话】
    • 异步通讯:不需要马上回复【像发邮件】
      在这里插入图片描述

1.1.1 同步通讯

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

  • 同步调用的优点:
    • 时效性较强,可以立即得到结果
  • 同步调用的问题:
    • 耦合度高
    • 性能和吞吐能力下降
    • 有额外的资源消耗
    • 有级联失败问题

1.1.2 异步通讯

  • 异步调用常见实现就是事件驱动模式

  • 为了解除事件发布者与订阅者之间的耦合,两者并不是直接通信,而是有一个中间人(Broker)。发布者发布事件到Broker,不关心谁来订阅事件。订阅者从Broker订阅事件,不关心谁发来的消息。
    在这里插入图片描述

  • Broker 是一个像数据总线一样的东西,所有的服务要接收数据和发送数据都发到这个总线上,这个总线就像协议一样,让服务间的通讯变得标准和可控。
    在这里插入图片描述

    • 在事件模式中,支付服务是事件发布者(publisher),在支付完成后只需要发布一个支付成功的事件(event),事件中带上订单id。订单服务和物流服务是事件订阅者(Consumer),订阅支付成功的事件,监听到事件后完成自己业务即可。
  • 好处:

    • 吞吐量提升:无需等待订阅者处理完成,响应更快速
      在这里插入图片描述

    • 故障隔离:服务没有直接调用,不存在级联失败问题
      在这里插入图片描述

    • 调用间没有阻塞,不会造成无效的资源占用

    • 耦合度极低,每个服务都可以灵活插拔,可替换
      在这里插入图片描述

    • 流量削峰:不管发布事件的流量波动多大,都由Broker接收,订阅者可以按照自己的速度去处理事件
      在这里插入图片描述

  • 动画展示:
    在这里插入图片描述

  • 缺点:

    • 架构复杂了,业务没有明显的流程线,不好管理
    • 需要依赖于Broker的可靠、安全、性能

1.2 技术对比:

  • MQ,中文是消息队列(MessageQueue),字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。
  • 比较常见的MQ实现:
    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka

  • 几种常见MQ的对比:
RabbitMQActiveMQRocketMQKafka
公司/社区RabbitApache阿里Apache
开发语言ErlangJavaJavaScala&Java
协议支持AMQP,XMPP,SMTP,STOMPOpenWire,STOMP,REST,XMPP,AMQP自定义协议自定义协议
可用性一般
单机吞吐量一般非常高
消息延迟微秒级毫秒级毫秒级毫秒以内
消息可靠性一般一般
  • 追求可用性:Kafka、 RocketMQ 、RabbitMQ
  • 追求可靠性:RabbitMQ、RocketMQ
  • 追求吞吐能力:RocketMQ、Kafka
  • 追求消息低延迟:RabbitMQ、Kafka

二 RabbitMQ入门

2.1 RabbitMQ单机部署

  • 在Centos7虚拟机中使用Docker来安装

2.1.1 下载镜像

  • 方式一:在线拉取
    docker pull rabbitmq:3-management
    
    • 1
  • 方式二:本地加载[需提前下载好文件]
    docker load -i xxx.tar
    
    • 1

在这里插入图片描述

2.1.2 安装MQ

  • 执行运行容器的docker命令

    docker run \
     -e RABBITMQ_DEFAULT_USER=itcast \
     -e RABBITMQ_DEFAULT_PASS=123321 \
     --name mq \
     --hostname mq1 \
     -p 15672:15672 \
     -p 5672:5672 \
     -d \
     rabbitmq:3-management
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 访问:登录页面
    在这里插入图片描述

  • 登陆后显示当前的状态:
    在这里插入图片描述

  • 新增用户:
    在这里插入图片描述

  • 但是新增的用户是没有虚拟主机的访问权限的
    在这里插入图片描述

  • 新建一个虚拟主机,并为新用户分配访问虚拟主机的权限
    在这里插入图片描述
    在这里插入图片描述

2.2 RabbitMQ的集群【补充】

2.2.1 集群分类

  • 在RabbitMQ的官方文档中,讲述了两种集群的配置方式:
    • 普通模式:普通模式集群不进行数据同步,每个MQ都有自己的队列、数据信息(其它元数据信息如交换机等会同步)。例如我们有2个MQ:mq1,和mq2,如果你的消息在mq1,而你连接到了mq2,那么mq2会去mq1拉取消息,然后返回给你。如果mq1宕机,消息就会丢失。
    • 镜像模式:与普通模式不同,队列会在各个mq的镜像节点之间同步,因此你连接到任何一个镜像节点,均可获取到消息。而且如果一个节点宕机,并不会导致数据丢失。不过,这种方式增加了数据同步的带宽消耗。

2.2.2 设置网络

  • 首先,我们需要让3台MQ互相知道对方的存在。分别在3台机器中,设置 /etc/hosts文件,添加如下内容:
    192.168.150.101 mq1
    192.168.150.102 mq2
    192.168.150.103 mq3
    
    • 1
    • 2
    • 3
  • 并在每台机器上测试,是否可以ping通对方:

2.3 MQ的基本结构

在这里插入图片描述

  • RabbitMQ中的一些角色:
    • publisher:生产者
    • consumer:消费者
    • exchange个:交换机,负责消息路由
    • queue:队列,存储消息
    • virtualHost:虚拟主机,隔离不同租户的exchange、queue、消息的隔离
  • 消息一旦消费就会从队列删除,RabbitMQ没有消息回朔功能

2.4 RabbitMQ消息模型

  • RabbitMQ官方提供了5种不同的消息模型:
    在这里插入图片描述

2.5 HelloWorld案例

  • 官方的HelloWorld是基于最基础的消息队列模型来实现的,只包括三个角色:
    • publisher:消息发布者,将消息发送到队列queue
    • queue:消息队列,负责接受并缓存消息
    • consumer:订阅队列,处理队列中的消息
      在这里插入图片描述
  • 实现步骤:
    1. 导入资料中的demo工程
    2. 运行publisher服务中的测试类PublisherTest中的测试方法testSendMessage()
    3. 查看RabbitMQ控制台的消息
    4. 启动consumer服务,查看是否能接收消息
  • 基本消息队列的消息发送流程:
    1. 建立connection
    2. 创建channel
    3. 利用channel声明队列
    4. 利用channel向队列发送消息
  • 基本消息队列的消息接收流程:
    1. 建立connection
    2. 创建channel
    3. 利用channel声明队列
    4. 定义consumer的消费行为handleDelivery()
    5. 利用channel将消费者与队列绑定

2.5.1 publisher实现

  • 思路:
    • 建立连接
    • 创建Channel
    • 声明队列
    • 发送消息
    • 关闭连接和channel
  • 代码实现:
    public class PublisherTest {
        @Test
        public void testSendMessage() throws IOException, TimeoutException {
            // 1.建立连接
            ConnectionFactory factory = new ConnectionFactory();
            // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
            factory.setHost("192.168.150.101");
            factory.setPort(5672);
            factory.setVirtualHost("/");
            factory.setUsername("itcast");
            factory.setPassword("123321");
            // 1.2.建立连接
            Connection connection = factory.newConnection();
    
            // 2.创建通道Channel
            Channel channel = connection.createChannel();
    
            // 3.创建队列
            String queueName = "simple.queue";
            channel.queueDeclare(queueName, false, false, false, null);
    
            // 4.发送消息
            String message = "hello, rabbitmq!";
            channel.basicPublish("", queueName, null, message.getBytes());
            System.out.println("发送消息成功:【" + message + "】");
    
            // 5.关闭通道和连接
            channel.close();
            connection.close();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

在这里插入图片描述

2.5.2 consumer实现

  • 代码思路:
    • 建立连接
    • 创建Channel
    • 声明队列
    • 订阅消息
  • 代码实现:
    package cn.itcast.mq.helloworld;
    
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class ConsumerTest {
    
        public static void main(String[] args) throws IOException, TimeoutException {
            // 1.建立连接
            ConnectionFactory factory = new ConnectionFactory();
            // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
            factory.setHost("192.168.150.101");
            factory.setPort(5672);
            factory.setVirtualHost("/");
            factory.setUsername("itcast");
            factory.setPassword("123321");
            // 1.2.建立连接
            Connection connection = factory.newConnection();
    
            // 2.创建通道Channel
            Channel channel = connection.createChannel();
    
            // 3.创建队列
            String queueName = "simple.queue";
            channel.queueDeclare(queueName, false, false, false, null);
    
            // 4.订阅消息
            channel.basicConsume(queueName, true, new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope,
                                           AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 5.处理消息
                    String message = new String(body);
                    System.out.println("接收到消息:【" + message + "】");
                }
            });
            System.out.println("等待接收消息。。。。");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

在这里插入图片描述

2.5.3 登录RabbitMQ查看消息

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

三 SpringAMQP

  • SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。
  • SpringAmqp的官方地址
    在这里插入图片描述
  • SpringAMQP提供了三个功能:
    • 自动声明队列、交换机及其绑定关系
    • 基于注解的监听器模式,异步接收消息
    • 封装了RabbitTemplate工具,用于发送消息

3.1 Basic Queue 简单队列模型

在父工程mq-demo中引入依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

3.1.1 消息发送

  • 首先配置MQ地址,在publisher服务的application.yml中添加配置:
    spring:
      rabbitmq:
        host: xxx.xxx.xxx # 主机名
        port: 5672 # 端口
        virtual-host: / # 虚拟主机
        username: itcast # 用户名
        password: xxx # 密码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 然后在publisher服务中编写测试类SpringAmqpTest,并利用RabbitTemplate实现消息发送:
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class SpringAmqpTest {
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        @Test
        public void testSimpleQueue() {
            // 队列名称
            String queueName = "simple.queue";
            // 消息
            String message = "hello, spring amqp!";
            // 发送消息
            rabbitTemplate.convertAndSend(queueName, message);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

3.1.2 消息接收

  • 首先配置MQ地址,在consumer服务的application.yml中添加配置:
    spring:
      rabbitmq:
        host: xxx.xxx.xxx.xxx # 主机名
        port: 5672 # 端口
        virtual-host: / # 虚拟主机
        username: itcast # 用户名
        password: xxx# 密码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 然后在consumer服务的cn.itcast.mq.listener包中新建一个类SpringRabbitListener,代码如下:
    @Component
    public class SpringRabbitListener {
    
        @RabbitListener(queues = "simple.queue")
        public void listenSimpleQueueMessage(String msg) throws InterruptedException {
            System.out.println("spring 消费者接收到消息:【" + msg + "】");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

3.1.3 测试

  • 启动consumer服务,然后在publisher服务中运行测试代码,发送MQ消息

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

3.2 WorkQueue

  • Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息
    在这里插入图片描述
  • 当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型,多个消费者共同处理消息处理,速度能大大提高。

3.2.1 消息发送

  • 在publisher服务中的SpringAmqpTest类中添加一个测试方法:

    /**
    * workQueue
    * 向队列中不停发送消息,模拟消息堆积。
    */
    @Test
    public void testWorkQueue() throws InterruptedException {
        // 队列名称
        String queueName = "simple.queue";
        // 消息
        String message = "hello, message_";
        for (int i = 0; i < 50; i++) {
            // 发送消息
            rabbitTemplate.convertAndSend(queueName, message + i);
            Thread.sleep(20);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

3.2.2 消息接收

  • 要模拟多个消费者绑定同一个队列,我们在consumer服务的SpringRabbitListener中添加2个新的方法:
    @RabbitListener(queues = "simple.queue")
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
        Thread.sleep(20);
    }
    
    @RabbitListener(queues = "simple.queue")
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
        Thread.sleep(200);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

3.2.3 测试

  • 启动ConsumerApplication后,在执行publisher服务中刚刚编写的发送测试方法testWorkQueue。
  • 可以看到消费者1很快完成了自己的25条消息。消费者2却在缓慢的处理自己的25条消息。消息是平均分配给每个消费者,并没有考虑到消费者的处理能力
  • 原因:由于消费预取模式的设定,两个消费者预取后再处理造成消耗。

3.2.4 消费预取限制

  • 修改consumer服务的application.yml文件,添加配置:
    spring:
      rabbitmq:
        listener:
          simple:
            prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
    
    • 1
    • 2
    • 3
    • 4
    • 5

在这里插入图片描述

3.2.5 总结

  • Work模型的使用:
    • 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
    • 通过设置prefetch来控制消费者预取的消息数量

3.3 发布/订阅

  • 发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。实现方式是加入了exchange(交换机)。

  • 发布订阅的模型如图:
    在这里插入图片描述

  • Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)

  • Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有以下3种类型:

    • Fanout:广播,将消息交给所有绑定到交换机的队列
    • Direct:定向,把消息交给符合指定routing key 的队列
    • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
  • Consumer:消费者,与以前一样,订阅队列,没有变化

  • Queue:消息队列也与以前一样,接收消息、缓存消息。


  • Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

3.4 Fanout Exchange 广播

  • Fanout Exchange 会将接收到的消息广播到每一个跟其绑定的queue
    在这里插入图片描述
  • 在广播模式下,消息发送流程是这样的:
    • 1) 可以有多个队列
    • 2) 每个队列都要绑定到Exchange(交换机)
    • 3) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定
    • 4) 交换机把消息发送给绑定过的所有队列
    • 5) 订阅队列的消费者都能拿到消息

3.5 FanoutExchange案例

  • 实现思路如下:
    1. 在consumer服务中,利用代码声明队列、交换机,并将两者绑定
    2. 在consumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
    3. 在publisher中编写测试方法,向itcast.fanout发送消息

3.5.1 声明队列和交换机

在这里插入图片描述

  • 在consumer中创建一个类,声明队列和交换机:
    @Configuration
    public class FanoutConfig {
        /**
         * 声明交换机
         * @return Fanout类型交换机
         */
        @Bean
        public FanoutExchange fanoutExchange(){
            return new FanoutExchange("itcast.fanout");
        }
    
        /**
         * 第1个队列
         */
        @Bean
        public Queue fanoutQueue1(){
            return new Queue("fanout.queue1");
        }
    
        /**
         * 绑定队列和交换机
         */
        @Bean
        public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
            return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
        }
    
        /**
         * 第2个队列
         */
        @Bean
        public Queue fanoutQueue2(){
            return new Queue("fanout.queue2");
        }
    
        /**
         * 绑定队列和交换机
         */
        @Bean
        public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
            return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

3.5.2 消息发送

  • 在publisher服务的SpringAmqpTest类中添加测试方法:
    @Test
    public void testFanoutExchange() {
        // 队列名称
        String exchangeName = "itcast.fanout";
        // 消息
        String message = "hello, everyone!";
        rabbitTemplate.convertAndSend(exchangeName, "", message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

在这里插入图片描述

3.5.3 消息接收

  • 在consumer服务的SpringRabbitListener中添加两个方法,作为消费者:
    @RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String msg) {
        System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
    }
    
    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String msg) {
        System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

在这里插入图片描述

3.5.4 总结

  • 交换机的作用是什么?
    • 接收publisher发送的消息
    • 将消息按照规则路由到与之绑定的队列
    • 不能缓存消息,路由失败,消息丢失
    • FanoutExchange的会将消息路由到每个绑定的队列
  • 声明队列、交换机、绑定关系的Bean是什么?
    • Queue
    • FanoutExchange
    • Binding

3.6 Direct Exchange 路由

  • Direct Exchange 会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routes)。
    • 每一个Queue都与Exchange设置一个BindingKey
    • 发布者发送消息时,指定消息的RoutingKey
    • Exchange将消息路由到BindingKey与消息RoutingKey一致的队列
      在这里插入图片描述

3.7 DirectExchange案例

  • 实现思路如下:
    • 利用@RabbitListener声明Exchange、Queue、RoutingKey
    • 在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2
    • 在publisher中编写测试方法,向itcast. direct发送消息

3.7.1 基于注解声明队列和交换机

  • Spring还提供了基于注解方式来声明队列和交换机

  • 在consumer的SpringRabbitListener中添加两个消费者,同时基于注解来声明队列和交换机:

    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "direct.queue1"),
        exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
        key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
    }
    
    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "direct.queue2"),
        exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
        key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

3.7.2 消息发送

  • 在publisher服务的SpringAmqpTest类中添加测试方法:
    @Test
    public void testSendDirectExchange() {
        // 交换机名称
        String exchangeName = "itcast.direct";
        // 消息
        String message = "红色警报!";
        // 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "red", message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

3.7.3 总结

  • 描述下Direct交换机与Fanout交换机的差异?

    • Fanout交换机将消息路由给每一个与之绑定的队列
    • Direct交换机根据RoutingKey判断路由给哪个队列
    • 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
  • 基于@RabbitListener注解声明队列和交换机有哪些常见注解?

    • @Queue
    • @Exchange

3.8 Topic Exchange 主题

  • Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!
  • Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
  • 通配符规则:
    • #:匹配一个或多个词
    • *:匹配不多不少恰好1个词

在这里插入图片描述

3.9 Topic Exchange案例

  • 实现思路如下:
    1. 并利用@RabbitListener声明Exchange、Queue、RoutingKey
    2. 在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2
    3. 在publisher中编写测试方法,向itcast. topic发送消息

在这里插入图片描述

3.9.1 消息发送

  • 在publisher服务的SpringAmqpTest类中添加测试方法:
    /**
    * topicExchange
    */
    @Test
    public void testSendTopicExchange() {
        // 交换机名称
        String exchangeName = "itcast.topic";
        // 消息
        String message = "喜报!我国正大步向社会主义强国迈进!";
        // 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

3.9.2 消息接收

  • 在consumer服务的SpringRabbitListener中添加方法:
    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "topic.queue1"),
        exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
        key = "china.#"
    ))
    public void listenTopicQueue1(String msg){
        System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
    }
    
    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "topic.queue2"),
        exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
        key = "#.news"
    ))
    public void listenTopicQueue2(String msg){
        System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

在这里插入图片描述

3.9.3 总结

  • 描述下Direct交换机与Topic交换机的差异?
    • Topic交换机接收的消息RoutingKey必须是多个单词,以 **.** 分割
    • Topic交换机与队列绑定时的bindingKey可以指定通配符
    • #:代表0个或多个词
    • *:代表1个词

3.10 消息转换器

  • Spring会把你发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象。
  • 只不过,默认情况下Spring采用的序列化方式是JDK序列化。众所周知,JDK序列化存在下列问题:
    • 数据体积过大
    • 有安全漏洞
    • 可读性差

3.10.1 测试默认转换器

  • 发送一个Map对象
    @Test
    public void testSendMap() throws InterruptedException {
        // 准备消息
        Map<String,Object> msg = new HashMap<>();
        msg.put("name", "Jack");
        msg.put("age", 21);
        // 发送消息
        rabbitTemplate.convertAndSend("simple.queue","", msg);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 停止consumer服务,发送消息后查看控制台:
    在这里插入图片描述

3.10.2 配置JSON转换器

  • JDK序列化方式并不合适,因此可以使用JSON方式来做序列化和反序列化,使消息体的体积更小、可读性更高
  • 在publisher和consumer两个服务中都引入依赖
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.9.10</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 在publisher和consumer两个服务中都配置消息转换器
    • 注意:发送方和接收方都必须使用相同的MessageConverter
    @Bean
    public MessageConverter jsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }
    
    • 1
    • 2
    • 3
    • 4
  • 停止consumer服务,发送消息后查看控制台:
    在这里插入图片描述
  • 定义消费者,监听队列并消费消息
    @RabbitListener(queues = "object.queue")
        public void listenObjectQueue(Map<String, Object> msg){
            System.out.println("消费者接收到object.queue的消息:【" + msg + "】");
        }
    
    • 1
    • 2
    • 3
    • 4
  • 演示结果
    在这里插入图片描述
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/390916
推荐阅读
相关标签
  

闽ICP备14008679号