【RabbitMQ基础篇】RabbitMQ从入门到实战


精选专栏链接 🔗


欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰

更多内容持续更新中!希望能给大家带来帮助~

【RabbitMQ基础篇】RabbitMQ从入门到实战


1,同步调用和异步调用

  • 同步调用:如同打视频电话,双方的交互都是实时的。因此同一时刻你只能跟一个人打视频电话;
  • 异步调用:如同发微信聊天,双方的交互不是实时的,你不需要立刻给对方回应。因此你可以多线操作,同时跟多人聊天;
  • 两种方式各有优劣,打电话可以立即得到响应,但是你却不能跟多个人同时通话。发微信可以同时与多个人收发微信,但是往往响应会有延迟。

2,为什么需要异步调用

Feign 调用是我们之前常采用的微服务调用方式,这种调用中,调用者发起请求后需要等待 服务提供者执行业务返回结果后,才能继续执行后面的业务。也就是说调用者在调用过程中处于阻塞状态,因此 Feign 调用属于同步调用 。但在很多场景下,我们可能需要采用异步调用的方式。如下图是一个用户登陆的流程:

  • 首先用户点击登录;
  • 用户微服务查询并校验用户信息;

到这一步,校验完成实际已经基本登陆成功,但在实际开发场景中 ,还要做一些其他的操作。比如:

  • 调用风控微服务校验登录信息,看账户是否存在安全风险;
  • 如果存在风控信息,调用短信微服务给用户发送短信;

上述过程,如果采用 Feign 类似的同步调用,用户需要等全部操作完成之后才能登陆,可能要等很长时间,效率低。因此可以采用如下图所示的异步通讯模式:

  • 当用户微服务校验登录信息,校验通过后,将登录用户的信息向MQ发送,此时登陆业务结束;
  • 风控微服务和短信微服务监听 MQ 消息,收到消息后去做对应的操作;
  • 显然此场景下,这种异步方式效率更高;

所以,如果我们的业务需要实时得到服务提供方的响应,则应该选择同步通讯(同步调用)。而如果我们追求更高的效率,并且不需要实时响应,则应该选择异步通讯(异步调用)


3,消息队列

消息队列(Message Queue,MQ) 是一种跨进程、跨系统通信的中间件模型。它的核心思想是异步传递消息,让发送方和接收方无需同时在线、也无需直接耦合。


3.1,MQ的核心角色

异步调用方式其实就是基于MQ消息队列 的方式,MQ 一般包含三个角色:

  • 消息发送者:投递消息的人,就是原来的调用方,也可以叫消息的生产者;
  • 消息Broker:也叫消息代理。负责管理、暂存、转发消息。 如:RabbitMQ、ActiveMQ ;
  • 消息接收者: 接收和处理消息的人,就是原来的服务提供方。也可以叫消息的消费者;

3.2,异步调用的优缺点

综上,异步调用的优势包括:

  • 耦合度更低;
  • 性能更好,响应更快;
  • 业务拓展性强。消息发送者只负责发送消息到MQ,增加新业务时,消息发送者逻辑无需修改;
  • 故障隔离,避免级联失败。消息接收者故障时,可以待故障消除后继续获取 MQ 消息并执行;
  • 缓存消息,流量削峰填谷。假如消息接收者压力突然增大,消息 Broker 会缓存发过来的信息,后续的消息接收者可以根据自己的处理速度逐步进行处理,从而减轻压力。

当然,异步通信也并非完美无缺,它存在下列缺点:

  • 时效性差,不能立即得到调用结果;
  • 不确定下游业务执行是否成功;
  • 完全依赖于Broker的可靠性、安全性和性能;
  • 架构复杂,后期维护和调试麻烦;

3.3,MQ的技术选型

几种常见的 MQ 技术的对比:

RabbitMQ ActiveMQ RocketMQ Kafka
公司/社区 Rabbit Apache 阿里 Apache
开发语言 Erlang Java Java Scala & Java
协议支持 AMQP,XMPP,SMTP,STOMP OpenWire,STOMP,REST,XMPP,AMQP 自定义协议 自定义协议
可用性 一般
单机吞吐量 一般 非常高
消息延迟 微秒级 毫秒级 毫秒级 毫秒以内
消息可靠性 一般 一般
  • 追求可用性:Kafka、 RocketMQ 、RabbitMQ;
  • 追求可靠性:RabbitMQ、RocketMQ;
  • 追求吞吐能力:RocketMQ、Kafka;
  • 追求消息低延迟:RabbitMQ、Kafka;

4, RabbitMQ简介

4.1,RabbitMQ的核心概念及整体架构

RabbitMQ中包含几个核心概念:

  • publisher:生产者,也就是发送消息的一方;
  • consumer:消费者,也就是消费消息的一方;
  • queue:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理;
  • exchange:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列;
  • virtual host:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue。有了这一概念,多个项目可以共用RabbitMQ;

上述这些东西都可以在RabbitMQ的管理控制台来管理,下一节我们就一起来学习控制台的使用。

RabbitMQ对应的架构如下图所示:


4.2,使用RabbitMQ控制台收发消息

需求:在 RabbitMQ 的控制台完成下列操作

  • 新建队列 hello.queue1、hello.queue2;
  • 向默认的交换机 amp.fanout 交换机发送一条消息;
  • 查看消息是否到达队列 hello.queue1、hello.queue2;

① 在控制台,按照如下的步骤进行操作,新建队列 hello.queue1、hello.queue2:

队列新建后,可以在页面上看到新建的队列信息,如下图所示:

但此时向某个交换机发消息,消息是无法到达新建的这两个队列。如果想让消息正常路由到这两个队列还需要配置队列和交换机的关系。

② 配置队列和交换机的绑定关系


③ 发送消息进行测试




4.3,RabbitMQ的数据隔离

前面我们提到过,RabbitMQ里有虚拟主机(virtual host)的概念,虚拟主机可实现数据隔离。

需求:在RabbitMQ的控制台完成以下操作

  • 新建一个用户 hmall;
  • 为 hmall 用户创建一个 virtual host;
  • 测试不同 virtual host 之间的数据隔离现象;


需要新添加的用户登录自己的账户,创建虚拟主机。如下图,切换为 hmall 登录,并添加虚拟主机:




此后,虚拟主机可以添加自己的队列。


5,Spring AMQP

由于RabbitMQ采用了AMQP协议,因此它具备跨语言的特性。任何语言只要遵循AMQP协议收发消息,都可以与RabbitMQ交互。并且RabbitMQ官方也提供了各种不同语言的客户端。

但是,RabbitMQ官方提供的Java客户端编码相对复杂,一般生产环境下我们更多会结合Spring来使用。而Spring的官方刚好基于RabbitMQ提供了这样一套消息收发的模板工具:SpringAMQP。并且还基于SpringBoot对其实现了自动装配,使用起来非常方便

Spring Amqp的官方地址: https://spring.io/projects/spring-amqp

Spring AMQP提供了三个功能:

  • 自动声明队列、交换机及其绑定关系;
  • 基于注解的监听器模式,异步接收消息;
  • 封装了RabbitTemplate工具,用于发送消息;

5.1,快速收发消息

在之前的案例中,我们都是经过交换机发送消息到队列,此处为了测试方便,我们直接向队列发送消息,跳过交换机进行演示:

① 项目中使用 Spring AMQP 时,需要引入其依赖:

xml 复制代码
<dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

② 用到RabbitMQ 的各微服务中,需要在配置文件内添加 RabbitMQ 配置信息:

yaml 复制代码
spring:
  rabbitmq:
    host: your_ip   # 主机地址
    port: 5672  # 端口号
    virtual-host: /hmall  # 虚拟主机名
    username: hmall  # 用户名
    password: 123  # 密码

注意:

  • RabbitMQ的 5672 端口才是用来做消息通信的 ;
  • RabbitMQ的 15672 端口是 Web 管理界面(控制台)的端口;

③ 通过控制台创建队列 simple.queue

④ 使用 RabbitTemplate 工具类发送消息

java 复制代码
@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void testSendMessage2Queue() {
        String queueName = "simple.queue";
        String msg = "hello, amqp!";
        rabbitTemplate.convertAndSend(queueName, msg);
    }
}

运行上述代码,查看RabbitMQ控制台:

接收消息

在消费者微服务的application.yml中也要添加 RabbitMQ 的配置:

yaml 复制代码
spring:
  rabbitmq:
    host: your_ip 
    port: 5672 
    virtual-host: /hmall # 虚拟主机
    username: hmall # 用户名
    password: 123 # 密码

Spring AMQP 提供声明式的消息监听,我们只需要通过 @RabbitListener 注解在方法上声明要监听的队列名称,将来 Spring AMQP 就会把消息传递给当前方法。

java 复制代码
@Slf4j
@Component
public class MqListener {

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(String msg){
        System.out.println("消费者收到了simple.queue的消息:【" + msg +"】");
    }
}

⑥ 此时通过生产者和消费者的两个微服务,进行收发消息测试:

启动 consumer 微服务

在 publisher 微服务的测试类中运行前面定义的 testSendMessage2Queue 方法,向队列中发送消息:

在 consumer 控制台查看是否监听到消息。

上述演示使用的 mq-demo 完整 Java 项目链接如下:

bash 复制代码
通过网盘分享的文件:mq-demo.zip
链接: https://pan.baidu.com/s/1e9hM32sxos_mLGaB9OtJ2w?pwd=yyds 提取码: yyds

5.2,Work Queues 模式

Work queues,任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。分摊消息处理的压力,加快消息处理速度

接下来我们模拟 Work Queue ,实现一个队列绑定多个消费者。

① 在 RabbitMQ 的控制台创建一个队列,名为 work.queue;

② 在 publisher 服务中定义测试方法,在1秒内产生50条消息,发送到 work.queue;

java 复制代码
    @Test
    void testWorkQueue() throws InterruptedException {
        String queueName = "work.queue";
        for (int i = 1; i <= 50; i++) {
            String msg = "hello, worker, message_" + i;
            rabbitTemplate.convertAndSend(queueName, msg);
            Thread.sleep(20);  // 20ms发送一个消息,1s发送50条消息
        }
    }

③ 在 consumer 微服务的 MqListener 类中编写代码

java 复制代码
    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1 收到了 work.queue的消息:【" + msg +"】");
        Thread.sleep(20);
    }

    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2 收到了 work.queue的消息...... :【" + msg +"】");
        Thread.sleep(200);
    }

listenWorkQueue1 方法休眠 20ms,listenWorkQueue2 方法休眠200ms,这样写来模拟不同消费者处理消息能力的优劣。消费者1 处理能力较强,消费者 2 处理能力相对较弱。

④ 测试收发消息

启动 consumer 微服务,并在 publisher 微服务中运行测试方法 testWorkQueue ,查看consumer 微服务的控制台:

从控制台输出的结果可以看出,消费者1和消费者2竟然每人消费了25条消息:

  • 消费者1很快完成了自己的25条消息;
  • 消费者2却在缓慢的处理自己的25条消息;

因为默认情况下 RabbitMQ 采用负载均衡策略均摊消息给多个消费者。这会造成:一个消费者空闲,另一个消费者忙的不可开交,没有充分利用每一个消费者的能力,会出现消息堆积的现象,我们更希望实现的是能者多劳,处理能力强的消费者可以多处理一些消息。

在spring中有一个简单的配置,可以解决这个问题。我们修改consumer服务的application.yml文件,添加配置:

yml 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

⑤ 修改完配置,再次测试收发消息


5.3,交换机

在之前的测试案例中,为了方便测试,都没有引入交换机,生产者直接发送消息到队列,而真正的生产环境都会经过 exchange 来发送消息。不引入交换机的方式可能会出现问题,比如:

开发场景: 用户完成支付后发送一条MQ消息,多个微服务监听消息队列并完成相应的操作,比如订单微服务修改订单状态、通知微服务给用户发送通知、积分微服务增加用户积分...此时如果没有交换机,支付服务直接发消息到消息到队列,多个微服务都在监听此队列的情况下,只能有一个微服务能处理信息,处理完消息就没有了,其他微服务无法收到支付成功的通知。 这显然是不合理的。

RabbitMQ 交换机的类型主要有以下三种:

  • Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机;
  • Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列;
  • Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符;

5.3.1,Fanout 交换机

Fanout Exchange 会将接收到的消息广播到每一个跟其绑定的queue,所也叫广播模式。

在前面提到的支付开发场景下的问题,可以使用 Fanout 交换机可解决:支付成功之后,向 Fanout 交换机里发一条消息,为订单、积分和通知微服务分别创建一个队列,这样做三个微服务都能收到支付成功的通知 。

接下来,我们利用 Spring AMQP 演示 FanoutExchange 的使用。

① 在RabbitMQ 控制台中,创建两个队列 fanout.queue1 和 fanout.queue2

② 在RabbitMQ 控制台中,声明交换机 hmall.fanout,将两个队列与其绑定


③ 在 consumer 服务中,编写两个监听器方法,分别监听队列 fanout.queue1 和 fanout.queue2

java 复制代码
    @RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1 收到了 fanout.queue1的消息:【" + msg +"】");
    }

    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String msg) throws InterruptedException {
        System.out.println("消费者2 收到了 fanout.queue2的消息:【" + msg +"】");
    }

④ 在publisher 微服务中编写单元测试方法,向 hmall.fanout 发送消息

java 复制代码
    @Test
    void testSendFanout() {
        String exchangeName = "hmall.fanout";
        String msg = "hello, everyone!";
        rabbitTemplate.convertAndSend(exchangeName, null, msg);
    }

⑤ 联合测试

启动 consumer 微服务,并在publisher 微服务内运行 testSendFanout 单元测试方法,观察consumer 微服务控制台的输出,如下图所示即成功监听到消息:


5.3.2,Direct 交换机

Direct Exchange 会将接收到的消息根据规则路由到指定的Queue,因此称为定向路由。其规则如下:

  • 每一个 Queue 都与 Exchange 设置一个 BindingKey;
  • 发布者发送消息时,指定消息的 RoutingKey;
  • Exhcange 将消息路由到 BingKey 与消息 RoutingKey 一致的队列;

接下来利用 Spring AMQP 演示Direct Exchange的使用:

① 在 RabbitMQ 的控制台,声明队列 direct.queue1 和 direct.queue2;

② 在 RabbitMQ 的控制台,声明交换机 hmall.direct,将两个队列与其绑定;

如上图所示,给队列 direct.queue1 绑定的 key 是 blue 和 red;给队列 direct.queue2 绑定的 key 是 yellow 和 red;

③ 在consumer 服务中,编写两个消费者方法,分别监听 direct.queue1 和 direct.queue2

java 复制代码
    @RabbitListener(queues = "direct.queue1")
    public void listenDirectQueue1(String msg) {
        System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(queues = "direct.queue2")
    public void listenDirectQueue2(String msg) {
        System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
    }

④ publisher 微服务中编写单元测试方法发送消息到交换机

publisher 执行下面这段单元测试代码:

java 复制代码
    @Test
    void testSendDirect() {
        String exchangeName = "hmall.direct";
        String msg = "【红色警报】";
        rabbitTemplate.convertAndSend(exchangeName, "red", msg);
    }

由于两个队列都绑定了 red 。因此 两个消费者都能监听到:

publsiher 执行下面这段单元测试代码:

java 复制代码
    @Test
    void testSendDirect() {
        String exchangeName = "hmall.direct";
        String msg = "【蓝色通知】";
        rabbitTemplate.convertAndSend(exchangeName, "blue", msg);
    }

由于只有 direct.queue1绑定了blue,因此只有 direct.queue1 能监听到消息:


5.3.3,Topic 交换机

Topic Exchange 与 Direct Exchange 类似,区别在于Topic Exchange 中,routing Key 可以是多个单词的列表,单词之间以 .分割,且绑定BindingKey的时候可以使用通配符。 通配符规则如下:

  • #:匹配一个或多个词。
  • *:匹配单个词

举例:

  • item.#:能够匹配item.spu.insert 或者 item.spu
  • item.*:能匹配item.spu

如下图所示,queue1 只关心中国相关的消息;queue3 只关心天气相关的消息...

接下来,利用 Spring AMQP 演示 Topic Exchange 的使用。

① 在RabbitMQ 控制台中,声明队列 topic.queue1 和topic.queue1;

② 在RabbitMQ 控制台中,声明交换机 hmall.topic,将两个队列与其绑定;


topic.queue1 绑定了 china 开头的 key ;topic.queue2 绑定了 news 结尾的 key ;

③ 在consumer 服务中,编写两个监听器方法,分别监听 topic.queue1 和topic.queue1;

java 复制代码
    @RabbitListener(queues = "topic.queue1")
    public void listenTopicQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1 收到了 topic.queue1的消息:【" + msg +"】");
    }

    @RabbitListener(queues = "topic.queue2")
    public void listenTopicQueue2(String msg) throws InterruptedException {
        System.out.println("消费者2 收到了 topic.queue2的消息:【" + msg +"】");
    }

④ 在publisher微服务中,发布消息

执行如下单元测试代码,向 hmall.topic 交换机发送消息:

java 复制代码
    @Test
    void testSendTopic() {
        String exchangeName = "hmall.topic";
        String msg = "新闻联播即将开始";
        rabbitTemplate.convertAndSend(exchangeName, "japan.news", msg);
    }

显然能监听到的是 listenTopicQueue2 即消费者2可以收到信息。consumer 微服务控制台信息如下图所示:

如果执行如下单元测试代码,向 hmall.topic 交换机发送消息:

java 复制代码
    @Test
    void testSendTopic() {
        String exchangeName = "hmall.topic";
        String msg = "新闻联播即将开始";
        rabbitTemplate.convertAndSend(exchangeName, "china.news", msg);
    }

此时既满足china开头,又满足news结尾,此时两个队列都会收到消息,消费者1和消费者2都能监听到。consumer 微服务控制台信息如下图所示:


5.4,代码方式创建队列和交换机

前面的内容中,我们都是基于RabbitMQ控制台来创建队列和交换机。但是在实际开发时,手动输入极易出错。因此推荐的做法是由程序启动时检查队列和交换机是否存在,如果不存在自动创建。

Spring AMQP 提供了几个类,用来声明队列、交换机及其绑定关系:

  • Queue:用于声明队列,可以用工厂类 QueueBuilder 构建;
  • Exchange:用于声明交换机,可以用工厂类 ExchangeBuilder 构建;
  • Binding:用于声明队列和交换机的绑定关系,可以用工厂类 BingingBuilder 构建;

5.4.1,基于Bean的方式声明队列和交换机

在 consumer 微服务中创建一个配置类,声明队列和交换机( 以Fanout交换机为例 ):

java 复制代码
package com.itheima.consumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FanoutConfiguration {

		// 声明Fanout交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        // 也可用这种方式:ExchangeBuilder.fanoutExchange("hmall.fanout2").build();
        return new FanoutExchange("hmall.fanout2");
    }

		// 声明队列 fanoutQueue3
    @Bean
    public Queue fanoutQueue3(){
        // 也可用这种方式:QueueBuilder.durable("fanout.queue3").build();
        return new Queue("fanout.queue3");
    }

		// 绑定交换机和队列
    @Bean
    public Binding fanoutBinding3(Queue fanoutQueue3, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue3).to(fanoutExchange);
    }

		// 声明另外一个队列fanoutQueue4
    @Bean
    public Queue fanoutQueue4(){
        return new Queue("fanout.queue4");
    }

		// 绑定交换机和队列
    @Bean
    public Binding fanoutBinding4(){
    		/* 注意此处写的参数是fanoutQueue4(),Sprin会先去容器中看有无此bean,有的话就直接拿来,
    		* 如果没有才会执行此方法生成bean
    		*/
        return BindingBuilder.bind(fanoutQueue4()).to(fanoutExchange());
    }
}

此时启动 consumer 微服务,打开 RabbitMQ 控制台查看


在 consumer 微服务中创建一个配置类,声明队列和交换机( 以Direct交换机为例 ):

java 复制代码
package com.itheima.consumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DirectConfiguration {

    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("hmall.direct");
    }

    @Bean
    public Queue directQueue1(){
        return new Queue("direct.queue1");
    }

    @Bean
    public Binding directQueue1BindingRed(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
    }

    @Bean
    public Binding directQueue1BindingBlue(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
    }

    @Bean
    public Queue directQueue2(){
        return new Queue("direct.queue2");
    }

    @Bean
    public Binding directQueue2BindingRed(Queue directQueue2, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
    }

    @Bean
    public Binding directQueue2BindingBlue(Queue directQueue2, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
    }

}

显然这种基于Bean的方式创建队列和交换机存在缺点,代码量相对较多,每增加一个队列和绑定关系,都需要编写多个 Bean 方法。


5.4.2,基于注解的方式声明队列和交换机

基于@Bean的方式声明队列和交换机比较麻烦,因此Spring AMQP 还提供了基于 @RabbitListener 注解声明队列和交换机的方式。此方式更简洁方便

演示声明Direct模式的交换机和队列,consumer 微服务中监听器代码如下:

java 复制代码
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1", durable = "true"),  // 指定要绑定的队列
            // 其中durable = "true"表示需要持久化
            exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),  // 指定要绑定的交换机
            key = {"red", "blue"}  // 指定routing key
    ))  
    public void listenDirectQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1 收到了 direct.queue1的消息:【" + msg +"】");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2", durable = "true"),
            exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String msg) throws InterruptedException {
        System.out.println("消费者2 收到了 direct.queue2的消息:【" + msg +"】");
    }

5.5,消息转换器

我们测试一下利用 Spring AMQP 发送对象类型的消息。

① 声明一个队列,名为 object.queue ;

② 编写单元测试,向队列中直接发送一条消息,消息类型是Map;

java 复制代码
    @Test
    void testSendObject() {
        Map<String, Object> msg = new HashMap<>(2);
        msg.put("name", "jack");
        msg.put("age", 21);
        rabbitTemplate.convertAndSend("object.queue", msg);
    }

③ 在 RabbitMQ 控制台查看消息

此时发现控制台获取到的是如下的一堆编码:

显然,此时发送消息时使用的是 Java 原生的序列化方式(JDK Serialization),而不是通用的 JSON 格式。虽然这种方式可用于 Java 程序之间传输数据,但是存在很多缺点:

  • 不可读:在后台完全看不懂内容,无法调试;
  • 跨语言不兼容:如果消费者是用 Python、Go 或 Node.js 写的,它们无法识别 Java 的原生序列化格式,会导致反序列化失败;
  • 性能较差:Java 原生序列化的字节流通常比较大,且序列化和反序列化速度较慢;
  • 版本依赖:如果发送方和接收方的类定义(serialVersionUID)不一致,反序列化会报错;

5.5.1,JSON序列化代替默认的JDK序列化

因此,为了让消息在后台可读,并且支持跨语言,通常建议统一使用 JSON 格式。

首先,在 publisher 和 consumer 微服务中要引入 jackson 依赖:

xml 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

然后,还需要配置消息转换器,即在publisherconsumer两个服务的启动类中各自添加一个Bean即可:

java 复制代码
    @Bean
    public MessageConverter jacksonMessageConvertor(){
        return new Jackson2JsonMessageConverter();
    }

修改后重启 consumer 微服务,在 publisher 微服务中运行单元测试方法,查看 RabbitMQ 控制台

此时即可在控制台查看到 JSON 格式的数据:

消费者监听消息的代码如下:

java 复制代码
    @RabbitListener(queues = "object.queue")
    public void listenObject(Map<String, Object> msg) throws InterruptedException {
        System.out.println("消费者 收到了 object.queue的消息:【" + msg +"】");
    }

此时重启 consumer 微服务,控制台输出信息如下:

上述存在一个报错是因为 object.queue 队列中存在2个 message ,message 1 是 JDK的序列化格式(由于此时我们配置了JSON的序列化,因此会报错),message 2才是正常的符合当前项目配置的 json 格式,因此可以成功拿到。

相关推荐
MandalaO_O1 小时前
MySQL:数据库约束
数据库·mysql
上弦月-编程1 小时前
Java编程:跨平台开发利器
java·开发语言
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第38题:两个对象的hashCode()相同,则 equals()是否也一定为 true?
java·开发语言·后端·面试·hash-index
java1234_小锋1 小时前
什么是可重入锁ReentrantLock?
java·开发语言
江南十四行1 小时前
Java并发编程中的锁机制:synchronized与Lock详解
java·开发语言
SamDeepThinking1 小时前
所有的框架源码,最怕的就是被debug
java·后端·程序员
刘~浪地球1 小时前
MongoDB聚合管道进阶:数据处理与统计分析
数据库·mongodb
瀚高PG实验室1 小时前
debezium在LANG=zh_CN.UTF-8下,无法解析timestamp类型的列值为BC的字段
服务器·数据库·postgresql·瀚高数据库