0


RabbitMQ基础有这一篇就够了

RabbitMQ基础篇

1. 同步异步

异步调用通常是基于消息通知的方式,包含三个角色:

  • 消息发送者:投递消息的人,就是原来的调用者
  • 消息接收者:接收和处理消息的人,就是原来的*服务提供者
  • 消息代理:管理、暂存、转发消息,你可以把它理解成微信服务器

在这里插入图片描述

异步调用

在这里插入图片描述

异调用的优势是什么?

  • 耦合度低,拓展性强
  • 异步调用,无需等待,
  • 性能好 故障隔离,下游服务故障不影响上游业务
  • 缓存消息,流量削峰填谷

异步调用的问题是什么?

  • 不能立即得到调用结果,时效性差
  • 不确定下游业务执行是否成功
  • 业务安全依赖于Broker的可靠性

2. MQ技术选型

MQ (MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是异步调用中的Broker。

在这里插入图片描述
可用性:系统正常运行的时间占总时间的比例。高可用性的消息队列系统能够在发生故障时快速恢复,保证数据的访问和处理不中断。

  • RabbitMQ:使用集群和镜像队列可以提供较高的可用性,但需要额外的配置和管理。
  • ActiveMQ:支持多种消息持久化模式,可以通过配置提高系统的可用性。
  • Kafka:使用复制(Replication)机制来实现高可用性,数据会被复制到多个副本,通常至少三个。

单机吞吐量:指单个服务器节点在单位时间内可以处理的消息数量。

  • RabbitMQ:吞吐量受节点内存和磁盘的限制,通常在消息量不是非常大的时候表现良好,每秒十万左右
  • ActiveMQ:吞吐量也受限于单机性能,但可以使用更高的配置来提高吞吐量。
  • Kafka:设计上更倾向于高吞吐量,特别是对于批处理场景,可以支持每秒数百万的消息处理。

消息延迟:从消息发送到消息被消费者处理之间的时间长度。延迟会受到网络延迟、队列处理的负载以及消息本身处理时间的影响。

  • RabbitMQ:支持低延迟的消息传递,但处理的消息量增加时,延迟可能会增加。
  • ActiveMQ:延迟受限于消息队列的配置,合理配置可以保证较低的延迟。
  • Kafka:除非进行特殊配置,默认情况下Kafka可能会有较大的延迟,因为它是面向批处理的。但在实际使用中可以通过调整配置来降低延迟。

消息可靠性:消息是否能够可靠地传递到消费者,以及消费者是否能够正确地处理消息。

  • RabbitMQ:提供了多种消息确认机制(如事务、发布确认),可以确保消息被可靠地传递。
  • ActiveMQ:同样提供了消息持久化、事务和消息确认等机制来确保消息的可靠性。K
  • Kafka:通过复制机制来确保数据不会丢失,同时消费者可以通过ACK机制来确保消息的处理。

在实际选择消息队列技术时,需要根据项目的具体需求来决定。如果项目对消息的一致性和顺序传递要求高,同时消息量不大,可能倾向于选择 RabbitMQ 或 ActiveMQ。如果对数据传输效率和吞吐量有较高要求,并且容忍一定的消息延迟Kafka 可能是更好的选择。

3. 数据隔离

对于小型企业而言,出于成本考虑,通常只会搭建一套MQ集群,公司内的多个不同项目同时使用(相当于一个MySQL的不同DB)。这个时候为了避免互相干扰, 会利用virtual host的隔离特性,将不同项目隔离。一般会做两件事情:

  • 给每个项目创建独立的运维账号,将管理权限分离。
  • 给每个项目创建不同的virtual host,将每个项目的数据隔离。在这里插入图片描述

消息发送的注意事项有哪些?

  • 交换机只能路由消息,无法存储消息
  • 交换机只会路由消息,发送给与其绑定的队列,因此队列必须与交换机绑定

4. SpringAMQP

Advanced Message Queuing Protocol,是用于在应用程序之间传递业务消息的开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求。

SpringAMQP收发消息的流程总结:

  1. 引入spring-boot-starter-amqp依赖
<!--AMQP依赖,包含RabbitMQ--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>
  1. 配置rabbitmq服务端信息(consumer 和 publisher服务的application.yml中都需要添加配置)
spring:rabbitmq:host: 192.168.150.101 # 你的虚拟机IPport:5672# 端口virtual-host: /hmall # 虚拟主机username: hmall # 用户名password:123# 密码
  1. 利用RabbitTemplate发送消息
@SpringBootTestpublicclassSpringAmqpTest{@AutowiredprivateRabbitTemplate rabbitTemplate;@TestpublicvoidtestSimpleQueue(){// 队列名称String queueName ="simple.queue";// 消息String message ="hello, spring amqp!";// 发送消息
        rabbitTemplate.convertAndSend(queueName, message);}}

在这里插入图片描述

  1. 利用@RabbitListener注解声明要监听的队列,监听消息
packagecom.itheima.consumer.listener;importorg.springframework.amqp.rabbit.annotation.RabbitListener;importorg.springframework.stereotype.Component;@ComponentpublicclassSpringRabbitListener{// 利用RabbitListener来声明要监听的队列信息// 将来一旦监听的队列中有了消息,就会推送给当前服务,调用当前方法,处理消息。// 可以看到方法体中接收的就是消息体的内容@RabbitListener(queues ="simple.queue")publicvoidlistenSimpleQueueMessage(String msg)throwsInterruptedException{System.out.println("spring 消费者接收到消息:【"+ msg +"】");}}

5. WorkQueues模式

在这里插入图片描述

5.1 Work Queues

Work queues,任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息

在这里插入图片描述

消费者消息推送限制

默认情况下,RabbitMQ的会将消息依次轮询投递给绑定在队列上的每一个消费者,假如有50条消息,两个消费者1,2,那么就会均分预先分给每个消费者25条消息。但这并没有考虑到消费者是否已经处理完消息,可能出现消息堆积。因此我们需要修改application.yml,设置preFetch值为1,确保同一时刻最多投递给消费者1条消息:

spring:rabbitmq:host: 192.168.150.101 # 你的虚拟机IPport:5672# 端口virtual-host: /hmall # 虚拟主机username: hmall # 用户名password:123# 密码listener:simple:prefetch:1# 每次只能获取一条消息,处理完成才能获取下一个消息

5.2 交换机(发布/订阅模式)

交换机的作用:

  • 接收publisher发送的消息
  • 将消息按照规则路由到与之绑定的队列

在这里插入图片描述

  • Publisher:生产者,不再发送消息到队列中,而是发给交换机
  • Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
  • Queue:消息队列也与以前一样,接收消息、缓存消息。不过队列一定要与交换机绑定。
  • Consumer:消费者,与以前一样,订阅队列,没有变化

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

交换机的类型有四种:

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

5.2.1 Fanout交换机(广播)

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

在这里插入图片描述

利用SpringAMQP演示FanoutExchange的使用

需求如下:

  1. 在RabbitMQ控制台中,声明队列fanout.queue1和fanout.queue2
  2. 在RabbitMQ控制台中,声明交换机hmall.fanout,将两个队列与其绑定
  3. 在consumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
  4. 在publisher中编写测试方法,向hmall.fanout发送消息

在这里插入图片描述

  • 步骤1:在RabbitMQ控制台中,声明队列fanout.queue1和fanout.queue2

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

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

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

  • 步骤3:在consumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
@TestpublicvoidtestFanoutExchange(){// 交换机名称String exchangeName ="hmall.fanout";// 消息String message ="hello, everyone!";// 发送消息, 参数分别为:交换机名称, RoutingKey(暂时为空或者为null), 消息
    rabbitTemplate.convertAndSend(exchangeName,"", message);}
  • 步骤4:在publisher中编写测试方法,向hmall.fanout发送消息
@RabbitListener(queues ="fanout.queue1")publicvoidlistenFanoutQueue1(String msg){System.out.println("消费者1接收到Fanout消息:【"+ msg +"】");}@RabbitListener(queues ="fanout.queue2")publicvoidlistenFanoutQueue2(String msg){System.out.println("消费者2接收到Fanout消息:【"+ msg +"】");}

5.2.2 Direct交换机(定向)

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

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

在这里插入图片描述

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

  • Fanout交换机将消息路由给每一个与之绑定的队列
  • Direct交换机根据RoutingKey判断路由给哪个队列
  • 如果多个队列具有相同RoutingKey,则与Fanout功能类似

利用SpringAMQP演示DirectExchange的使用

需求如下

  1. 在RabbitMQ控制台中,声明队列direct.queue1和direct.queue2
  2. 在RabbitMQ控制台中,声明交换机hmall. direct ,将两个队列与其绑定,direct.queue1的bindingKey为blud和red,direct.queue2的bindingKey为yellow和red
  3. 在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2
  4. 在publisher中编写测试方法,利用不同的RoutingKey向hmall. direct发送消息
  • 步骤1:在RabbitMQ控制台中,声明队列direct.queue1和direct.queue2
  • 步骤2:在RabbitMQ控制台中,声明交换机hmall. direct ,将两个队列与其绑定,direct.queue1的bindingKey为blud和red,direct.queue2的bindingKey为yellow和red在这里插入图片描述在这里插入图片描述
  • 步骤3:在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2
@RabbitListener(queues ="direct.queue1")publicvoidlistenDirectQueue1(String msg){System.out.println("消费者1接收到direct.queue1的消息:【"+ msg +"】");}@RabbitListener(queues ="direct.queue2")publicvoidlistenDirectQueue2(String msg){System.out.println("消费者2接收到direct.queue2的消息:【"+ msg +"】");}
  • 步骤4:在publisher中编写测试方法,利用不同的RoutingKey向hmall. direct发送消息
@TestpublicvoidtestSendDirectExchange(){// 交换机名称String exchangeName ="hmall.direct";// 消息String message ="红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";// 发送消息, RoutingKey为red
    rabbitTemplate.convertAndSend(exchangeName,"red", message);}@TestpublicvoidtestSendDirectExchange(){// 交换机名称String exchangeName ="hmall.direct";// 消息String message ="最新报道,哥斯拉是居民自治巨型气球,虚惊一场!";// 发送消息, RoutingKey为blue
    rabbitTemplate.convertAndSend(exchangeName,"blue", message);}

5.2.3 Topic交换机(话题)

TopicExchange与DirectExchange类似,区别在于routingKey可以是多个单词的列表,并且以 “.” 分割。
Queue与Exchange指定BindingKey时可以使用通配符:

  • #:代指0个或多个单词
  • *:代指一个单词

在这里插入图片描述

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

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

利用SpringAMQP演示DirectExchange的使用

需求如下:

  1. 在RabbitMQ控制台中,声明队列topic.queue1和topic.queue2
  2. 在RabbitMQ控制台中,声明交换机hmall. topic ,将两个队列与其绑定
  3. 在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2
  4. 在publisher中编写测试方法,利用不同的RoutingKey向hmall. topic发送消息

在这里插入图片描述

步骤1:在RabbitMQ控制台中,声明队列topic.queue1和topic.queue2

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

在这里插入图片描述

步骤3:在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2

/**
 * topicExchange
 */@TestpublicvoidtestSendTopicExchange(){// 交换机名称String exchangeName ="hmall.topic";// 消息String message ="喜报!孙悟空大战哥斯拉,胜!";// 发送消息, RountingKey满足topic.queue1的Routing key:china.#
    rabbitTemplate.convertAndSend(exchangeName,"china.news", message);}

步骤4:在publisher中编写测试方法,利用不同的RoutingKey向hmall. topic发送消息

@RabbitListener(queues ="topic.queue1")publicvoidlistenTopicQueue1(String msg){System.out.println("消费者1接收到topic.queue1的消息:【"+ msg +"】");}@RabbitListener(queues ="topic.queue2")publicvoidlistenTopicQueue2(String msg){System.out.println("消费者2接收到topic.queue2的消息:【"+ msg +"】");}

6. 声明队列、交换机以及进行绑定

SpringAMQP提供了API和注解两种方式来创建交换机、声明队列以及进行绑定;以后都不需要基于RabbitMQ控制台来创建队列、交换机进行创建,由程序启动时检查队列和交换机是否存在,如果不存在自动创建。

6.1 基于API声明队列、交换机以及进行绑定

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

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

SpringAMQP提供了一个Queue类,用来创建队列
在这里插入图片描述

SpringAMQP还提供了一个Exchange接口,来表示所有不同类型的交换机

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

而在绑定队列和交换机时,则需要使用BindingBuilder来创建Binding对象:

在这里插入图片描述

声明一个Fanout类型的交换机,并且创建队列与其绑定

packagecom.itheima.consumer.config;importorg.springframework.amqp.core.Binding;importorg.springframework.amqp.core.BindingBuilder;importorg.springframework.amqp.core.FanoutExchange;importorg.springframework.amqp.core.Queue;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassFanoutConfig{/**
     * 声明交换机
     * @return Fanout类型交换机
     */@BeanpublicFanoutExchangefanoutExchange(){// return ExchangeBuilder.fanoutExchange("hmall.fanout").build();returnnewFanoutExchange("hmall.fanout");}/**
     * 第1个队列
     */@BeanpublicQueuefanoutQueue1(){// 来声明队列// QueueBuilder.durable("fanout.queue1").build();returnnewQueue("fanout.queue1");}/**
     * 绑定队列和交换机
     */@BeanpublicBindingbindingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){returnBindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}/**
     * 第2个队列
     */@BeanpublicQueuefanoutQueue2(){// 来声明队列// QueueBuilder.durable("fanout.queue2").build();returnnewQueue("fanout.queue2");}/**
     * 绑定队列和交换机
     */@BeanpublicBindingbindingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){returnBindingBuilder.bind(fanoutQueue2).to(fanoutExchange);}}

声明一个direct模式类型的交换机,并且创建队列与其绑定

packagecom.itheima.consumer.config;importorg.springframework.amqp.core.*;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassDirectConfig{/**
     * 声明交换机
     * @return Direct类型交换机
     */@BeanpublicDirectExchangedirectExchange(){returnExchangeBuilder.directExchange("hmall.direct").build();}/**
     * 第1个队列
     */@BeanpublicQueuedirectQueue1(){returnnewQueue("direct.queue1");}/**
     * 绑定队列和交换机
     */@BeanpublicBindingbindingQueue1WithRed(Queue directQueue1,DirectExchange directExchange){returnBindingBuilder.bind(directQueue1).to(directExchange).with("red");}/**
     * 绑定队列和交换机
     */@BeanpublicBindingbindingQueue1WithBlue(Queue directQueue1,DirectExchange directExchange){returnBindingBuilder.bind(directQueue1).to(directExchange).with("blue");}/**
     * 第2个队列
     */@BeanpublicQueuedirectQueue2(){returnnewQueue("direct.queue2");}/**
     * 绑定队列和交换机
     */@BeanpublicBindingbindingQueue2WithRed(Queue directQueue2,DirectExchange directExchange){returnBindingBuilder.bind(directQueue2).to(directExchange).with("red");}/**
     * 绑定队列和交换机
     */@BeanpublicBindingbindingQueue2WithYellow(Queue directQueue2,DirectExchange directExchange){returnBindingBuilder.bind(directQueue2).to(directExchange).with("yellow");}}

6.2 基于注解声明队列、交换机以及进行绑定

声明Direct模式的交换机和队列案例:

@RabbitListener(bindings =@QueueBinding(
    value =@Queue(name ="direct.queue1"),
    exchange =@Exchange(name ="hmall.direct", type =ExchangeTypes.DIRECT),
    key ={"red","blue"}))publicvoidlistenDirectQueue1(String msg){System.out.println("消费者1接收到direct.queue1的消息:【"+ msg +"】");}@RabbitListener(bindings =@QueueBinding(
    value =@Queue(name ="direct.queue2"),
    exchange =@Exchange(name ="hmall.direct", type =ExchangeTypes.DIRECT),
    key ={"red","yellow"}))publicvoidlistenDirectQueue2(String msg){System.out.println("消费者2接收到direct.queue2的消息:【"+ msg +"】");}

声明Topic模式的交换机和队列案例:

@RabbitListener(bindings =@QueueBinding(
    value =@Queue(name ="topic.queue1"),
    exchange =@Exchange(name ="hmall.topic", type =ExchangeTypes.TOPIC),
    key ="china.#"))publicvoidlistenTopicQueue1(String msg){System.out.println("消费者1接收到topic.queue1的消息:【"+ msg +"】");}@RabbitListener(bindings =@QueueBinding(
    value =@Queue(name ="topic.queue2"),
    exchange =@Exchange(name ="hmall.topic", type =ExchangeTypes.TOPIC),
    key ="#.news"))publicvoidlistenTopicQueue2(String msg){System.out.println("消费者2接收到topic.queue2的消息:【"+ msg +"】");}

7. MQ消息转换器

Spring的消息发送代码接收的消息体是一个Object:

在这里插入图片描述

而在数据传输时,会把发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象。默认情况下Spring采用的序列化方式是JDK序列化。JDK序列化存在下列问题:

  • 数据体积过大
  • 有安全漏洞
  • 可读性差

发送消息后查看控制台:
在这里插入图片描述

可以看到控制台现实的消息格式不友好,可以引入Jackson依赖进行解决。

publisherconsumer两个服务中都引入依赖:

<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.9.10</version></dependency>

配置消息转换器,在publisher和consumer两个服务的启动类中添加一个Bean即可()。消息转换器中添加的messageId可以便于我们将来做幂等性判断。

@BeanpublicMessageConvertermessageConverter(){// 1.定义消息转换器Jackson2JsonMessageConverter jackson2JsonMessageConverter =newJackson2JsonMessageConverter();// 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
    jackson2JsonMessageConverter.setCreateMessageIds(true);return jackson2JsonMessageConverter;}

问题提出

  1. 在6.1中使用API声明队列、交换机以及确定绑定关系。Direct交换机中如何确定绑定关系的,direct.queue1绑定hmall.direct,rountingKey为什么是red,bule,而不是red,yellow??? - 参数传递顺序?
标签: rabbitmq 分布式 java

本文转载自: https://blog.csdn.net/qq_43751200/article/details/141824122
版权归原作者 碳烤小肥羊。。。 所有, 如有侵权,请联系我们删除。

“RabbitMQ基础有这一篇就够了”的评论:

还没有评论