【MQ】学习笔记——消息队列(MQ)之 黑马RabbitMQ

目录

一.初步认识

1.介绍

1.1.同步、异步介绍

1.2.技术选型

2.安装部署

3.控制台操作

 3.1.Queue(队列)

3.2.Exchange(交换机)

4.数据的隔离

二.Java客户端操作

1.常规操作

1.1.发送消息

1.2.接收消息 

2.WorkQueue

3.三种交换机

3.1.广播交换机(Fanout Exchange)

3.2.定向交换机(Direct Exchange)

3.3.话题交换机(Topic Exchange)

4.声明队列交换机

4.1.Bean声明

4.2.@RabbitListener 注解声明

5.消息转换器

三.进阶操作(业务优化)

1.发送者的可靠性

1.1.发送者重连

1.2.发送者确认机制

2.MQ的可靠性

2.1.数据持久化

2.2.Lazy Queue(惰性队列)

3.接收者的可靠性

3.1.消费者确认机制

3.2.失败重试机制

3.3.业务幂等性

4.延迟消息

4.1.死信交换机

4.2.延迟消息插件


一.初步认识


1.介绍

1.1.同步、异步介绍

        同步调用就像你和她视频聊天,同步进行;异步调用像你每天的早安、晚安,她都已读不回。

        之前我们使用的 OpenFeign 远程调用属于同步调用,它的优点就是时效性强。而这种方式在处理高并发时存在问题,还有在调用链过长时,导致扩展性差、性能低、容易级联失败等问题。这就需要我们使用到新的技术——异步调用,而消息队列 MessageQueueMQ) 就很好的解决了这个问题,提高了效率。

异步通讯

消息发送者:投递消息的人,就是原来远程调用中的调用者

消息接收者:接收和处理消息的人,就是原来远程调用中的服务提供者

消息队列:管理、暂存、转发消息,相当于一个中间的服务器

优点:解耦合、减少耗时、故障隔离、缓存消息。

缺点:时效性差、无法保证后续业务成功、业务安全依赖于消息代理(broker)的可靠性。

1.2.技术选型

        以下是常用的 4 个 MQ 技术,而要学习的是其中的 RabbitMQ 技术。


2.安装部署

        我们选择使用 docker 将其部署到 Linux 虚拟机中,便于我们使用,需要先拉取到 RabbitMQ的镜像然后使用以下命令部署,也可使用资料中准备好的 tar 包加载进来再部署。

docker run \
 -e RABBITMQ_DEFAULT_USER=登录用户名\
 -e RABBITMQ_DEFAULT_PASS=登录密码\
 -v mq-plugins:/plugins \
 --name mq \
 --hostname mq \
 -p 15672:15672 \
 -p 5672:5672 \
 --network hm-net\ 
 -d \
 rabbitmq:3.8-management

        1.登录用户名密码自行设置。

        2.-v:数据卷的挂载。

        3.--name ---hostname:容器名 主机名。

        4.-p:端口

                4.1:15672:控制台的端口。

                4.2:5672:收发消息的端口。

        5.--network:网络(若不需要,可删除)。

        6.-d:后台运行。

        成功安装后运行,访问 虚拟机地址:端口号 进入到 RabbitMQ 的控制台,使用之前配置的用户名密码登录。

成功登陆控制台


3.控制台操作

        在后续的控制台操作示例中,我们主要会在 Exchange(交换机)、Queue(队列)、admin(管理员)这三个菜单中进行操作,而 RabitMQ 会在创建时生成一些默认的队列与交换机,方便我们进行操作。

 3.1.Queue(队列)

        在队列的页面中第一个展示的是所有的队列,可点击队列名查看详细信息并对其进行操作。

第二个是新建队列的展示,队列名是必填项。

        进入到队列详情中 Bindings 可以查看与交换机的绑定关系也可进行绑定,同时下面可以进行删除队列,查看队列接收的消息

3.2.Exchange(交换机)

        交换机界面与队列界面类似

         进入到交换机查看详情,在 Bindings 下输入队列的名称绑定 Exchange Queue 的关系,如果不绑定,则发送到交换机的信息会丢失,因为交换机只负责转发消息,不能存储

        在交换机的详情页的·Publish message 下发送信息,因为我们成功的绑定了队列与交换机的关系,所以成功后在队列中点击 Get Message 可以获取到发送的信息内容。


4.数据的隔离

        RabbitMQ 的数据隔离主要通过其内置的虚拟主机Virtual Host)功能来实现,不同的主机之间的数据不互通。可以给用户创建不同的虚拟主机来实现登录账号不同,则看到的数据也不同的效果。我们可以在 admin 页面下进行设置。

        在用户管理下可以查看所有的用户以及添加新的用户。其中,再添加用户时密码需输入两次确认,还需设置用户的权限(如 AdminMonitoring 等)。

        而新建的用户是没有虚拟主机的,并且无法操作属于别人的交换机、队列。所以需要创建虚拟主机并绑定到与之对应的用户上。(注意,在创建时会自动绑定到当前登录的用户)

        可以在此处切换想要查看的虚拟主机。 


二.Java客户端操作


1.常规操作

        1.1.发送消息

        我们已经基本了解了在控制台中的基本操作,但在业务开发中,主要是在 Java 的客户端中去进行开发,而想要在 Java 中进行操作可以使用 Spring AMQP 这套 API 规范来实现功能。

        收发消息

        1.引入依赖

    <dependencies>
        <!--AMQP依赖,包含RabbitMQ-->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
        <version>2.7.18</version>
    </dependencies>

        2.在 yml 配置文件中配置 RabbitMQ 服务端信息

         需要在发送者和接收者中都配置好,端口是 5672 而不是15672 ,不要配置错误。

        3.注入 RabbitTemplate 工具类,准备好参数,发送(队列要提前创建好)

         可以看到成功发送消息并接收到

        1.2.接收消息 

        声明一个类加上 @Component 注解注册成一个 Bean,类中方法加上 @RabbitListener 注解使之成为一个监听器,接收信息的参数类型与发送的类型对应,而对象也可传递,Spring 会自动实现从对象到消息的转换,我们只需对信息进行处理即可。

         可以看到成功接收到了信息并输出


2.WorkQueue

        WorkQueue,任务模型。就是让多个消费者绑定到一个队列上,共同消费队列中的消息,可以加快消息处理的速度,避免消息的堆积

        通过代码对同一个队列进行监听,并通过循环发送 50 次消息。

         可以看到,两个消费者接收消息的顺序十分规律,所以消息只能被处理一次,当有多个消费者监听同一个队列时,RabbitMQ 会按照轮询的方式将消息平均发送给这些消费者。即每个消费者会依次接收到一条消息,直至所有消息都被分配完毕。这种方式确保了消息的均衡分配,避免了某个消费者过载而其他消费者空闲的情况。

        需要注意的是,虽然 RabbitMQ 默认采用轮询方式分发消息,但在某些情况下,也可以通过配置实现其他分发策略,如公平分发。在公平分发模式下,RabbitMQ 会根据消费者的消费能力进行动态调整,确保处理能力强的消费者能够接收到更多的消息,而处理能力弱的消费者则接收到较少的消息。这种分发方式可以更加灵活地适应不同的应用场景和需求。

        例如我们调整不同消费者的处理速度,使其休眠不同的时间来模拟处理效率的差异,并且修改相关 yml 配置文件的配置,使同一时刻最多投递的消息数为 1,只有处理完成才能获取下一个信息。(能者多劳

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1

        结果就是处理较的会处理更多的消息,而处理的会处理少的消息,使总耗时大大降低,提高了消息处理的效率。


3.三种交换机

        可在控制台中新增交换机处选择类型(Type

3.1.广播交换机(Fanout Exchange)

  1. 工作原理
    • 广播交换机将消息发送到所有与之绑定的队列中,无论消息的路由键是什么。
    • 它实现了一对多的消息分发,类似于广播模式。
  2. 应用场景
    • 适用于需要广播消息的场景,例如实时消息发布、通知系统等。
    • 当需要将同一条消息发送给多个消费者时,可以使用广播交换机。

        发送时需填入个参数,分别为交换机名Routing Key消息,其中 Routing Key 可以为空。类似于广播模式,可以实现一条消息被多个消费者都处理的效果。

3.2.定向交换机(Direct Exchange)

  1. 工作原理
    • 定向交换机根据消息的路由键(Routing Key)将消息发送到与之匹配的队列中。
    • 如果消息的路由键与队列的绑定键(Binding Key完全匹配,那么消息将被发送到该队列中。
  2. 应用场景
    • 适合一对一的消息传递,例如日志处理、任务分发等。
    • 当需要确保消息准确发送到特定队列时,可以使用定向交换机。

        发送时需填入个参数,分别为交换机名Routing Key消息,根据队列与交换机之间的键来判断路由交由哪个队列去处理,如果多个队列具有相同的 路由键,则功能与 Fanout 交换机的功能类似。

3.3.话题交换机(Topic Exchange)

  1. 工作原理
    • 主题交换机根据消息的路由键和队列的绑定键的模式进行匹配。
    • 可以用通配符(*和#)来匹配多个路由键使拓展性更强,从而实现更灵活的消息路由。
    • 其中,* 表示一个词,# 表示一个或多个词。
  2. 应用场景
    • 适合主题订阅模型,例如邮件分类、日志级别过滤等。
    • 当需要根据消息的某个主题或类别进行路由时,可以使用主题交换机。

        假如存在 #.new china.# 这两个绑定键,那么如果路由键为 china.weather 则只有第二个会收到消息,如果路由键为 china.new 则两个都会收到消息。


4.声明队列交换机

4.1.Bean声明

        手动的声明队列与交换机的效率低下,且存在误输入的风险,可靠性并不高,所有要使用 Java 代码来声明队列与交换机来提高开发的效率。

        而在 Spring AMQP 中提供了对应的类用来声明队列、交换机、以及它们之间的绑定关系。

        1,Queue:用于声明队列,也可以使用工厂类 QueueBuilder 来构建。

        2,Exchange:用于声明交换机,也可以使用工厂类 ExchangeBuilder 来构建。

        3,Binding:用于声明两者之间的绑定关系,也可以使用工厂类 BindingBuilder 来构建。

注意:Exchange 只是一个接口,其具体的实现类分别是对应的几个不同类型的交换机,如 FanoutExchange DirectExchange TopicExchange 等等。

        声明方式

        创建一个 Configuration 配置类,在其中将队列、交换机、绑定关系分别配置成 Bean,返回对应的对象或使用工厂类构造,运行对应的启动类,就会自动的去创建与之对应队列、交换机、以及绑定关系

        其中绑定关系的构建是 BindingBuilder.bind(队列).to(交换机).with(RoutingKey) 这样的。

4.2.@RabbitListener 注解声明

        可以使用当时用于定义消费者的注解 @RabbitListener  来定义队列、交换机、及绑定关系,只需其中的 bindings 属性,在其中使用 @QueueBinding 注解进行定义。

        1.value = @Queue(...) 定义了队列的具体属性。
        2.exchange = @Exchange(...) 指定关联的交换机详情。
        3.key = {"hi"} 设置了绑定的路由键。

创建成功


5.消息转换器

        在 Spring AMQP 在内部进行消息转化的时候会使用 JDK 自带的序列化方式,这种方法存在着问题,首先 JDK 的序列化存在安全风险,反序列化时容易被代码注入,其次,序列化后的消息占用空间太多,可读性差。

        建议使用 JSON 序列化代替默认的 JDK 序列化。

        1.在消息的接收者和消费者中都引入 jackson 的依赖

        <!--jackson-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.4</version>
        </dependency>

        2. 在两者中都要配置 MessageConverter 成 Bean(可在启动类中配置)

 配置成功后的消息


三.进阶操作(业务优化)


1.发送者的可靠性

1.1.发送者重连

        有的时候由于网络波动,可能会出现发送者连接 MQ 失败的情况。我们可以通过手动配置开启连接失败后的重连机制,红色框中参数为默认值,可根据需要调整。

        关闭掉正在运行的 RabbitMQ,模拟无法连接的状况。可以看到基本每隔 2 秒尝试重新连接,总共连接了 3 次。间隔时间 = 连接超时时间 + 重试间隔时间,所以是 2 秒。

        缺点:Spring AMQP 提供的重试机制时阻塞式的重试,在多次重试等待的过程中,当前线程是被阻塞的,会增加业务耗时从而影响业务的性能,如果对性能有要求,可以禁用重连机制或进行合适的配置,也可使用异步线程来执行发送消息的代码。

1.2.发送者确认机制

        Spring AMQP 提供了 Publisher Confirm Publisher Return 两种确认机制,开启确认机制后,当发送者发送消息给 MQ 后,MQ 会返回确认结果给发送者。

        1.消息投递到了 MQ,但是路由失败,此时会返回 PublisherReturn 返回路由异常原因,然后返回 ACK,告知投递成功。(基本都是由于本身代码的问题,再次重发可能还会失败,所以返回 ACK 算投递成功)

        2.临时消息投递到了 MQ,并且入队成功,返回 ACK,告知投递成功。(临时消息不需要持久化,只要投递过去就算成功)

        3.持久消息投递到了 MQ,并且入队完成持久化,返回 ACK,告知投递成功。

        4.其他情况都会返回 NACK,告知投递失败。

实现发送者确认机制

        1.在发送者的 yml 配置文件中添加配置。

spring:
  rabbitmq:
    publisher-confirm-type: correlated #开启 publisher confirm 机制,并设置 confirm 类型
    publisher-returns: true #开启 publisher return 机制

  publisher-confirm-type 配置有三种类型:

        1)none:关闭 confirm 机制。

        2)simple:同步阻塞等待 MQ 的回执消息。

        3)correlated:MQ 异步回调方式返回回执消息。(常用

        2.每个 RabbitTemplate 只能配置一个 ReturnCallback,因此需要在项目启动过程中配置。

        使用 @PostConstruct 注解在初始化时执行该方法来配置消息发送失败时的回调。 

        3.发送消息,指定消息 ID、消息 ConfirmCallback

       CorrelationData 需要两个东西,一个是消息的唯一标识 ID,另一个是它的一个回调函数,而 ID 可以在创建时使用 UUID 工具类生成随机的 ID,而回调函数可以使用 addCallback 方法添加。onFailure 方法是 Spring AMQP 在内部处理 MQ 返回的结果中出现异常后的处理,几乎不发生。 onSuccess 方法是成功拿到处理结果后的处理。最后需要将回调函数传入发送消息方法中。

        因为是测试,运行完后会终止进程,就无法得到回调,所以要休眠 2 秒等待回调结果返回,同时也需要将日志级别调为 debug 级,才可以看到日志。

        缺点:会影响消息发送的效率,不建议开启或限制重试次数。


2.MQ的可靠性

        在默认情况下,RabbitMQ 会将接收到的信息保存在内存中以降低消息收发的延迟,这样就会导致两个问题。

        1.一旦 MQ 宕机,内存中的消息就会丢失。

        2.内存空间有限,当消费者故障或处理速度较慢时,会导致消息堆积,引起阻塞。

解决方法:

2.1.数据持久化

        交换机持久化:

        在默认情况下创建的交换机都是默认持久化的,也可以在创建时指定为临时的交换机。

         队列持久化:

        在默认情况下创建的队列都是默认持久化的,也可以在创建时指定为临时的队列。

        消息持久化:

        在默认情况下是非持久的,可以选择 2 发送持久化的消息,而 Spring AMQP 发送的消息默认是持久化的,我们也可以通过自定义构建消息来发送非持久化的消息。

Message message = MessageBuilder
        .withBody("holle, SpringAMQP".getBytes(StandardCharsets.UTF_8))
        .setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT)
        .build();

         其中 setDeliveryMode  用于设置投递模式为持久化或非持久化。

         持久化的优点在于重启后,持久化的交换机、队列、消息仍然会存在,提高了效率。

2.2.Lazy Queue(惰性队列)

        在 RabbitMQ 3.6.0 版本增加的概念,在 3.12 版本后默认所有队列都是 Lazy Queue 模式,无法更改Lazy Queue 在接收到信息后直接存入到磁盘,不在存储在内存中,消费者要消费信息时才会从磁盘中读取并加载到内存(可以提前缓存部分消息到内存,最多为2048条)

旧版本更换 Lazy Queue 模式:

        1.控制台:

        在新建队列界面下的 Arguments 选项中选择 Lazy mode 模式创建。

        2.Java客户端:

        2.1.Bean注解

        2.2.@RabbitListener注解

        在开启持久化和生产者确认时,RabbitMQ 只有在消息持久化完成之后才会给生产者返回 ACK 回执。 


3.接收者的可靠性

3.1.消费者确认机制

        消费者确认机制是为了确认消费者是否成功处理消息。当消费者处理消息结束后应该向 RabbitMQ 发送一个回执,告知 RabbitMQ 自己消息的处理状态。

        ack:成功处理消息,RabbitMQ 从队列中删除该消息。

        nack:消息处理失败,RabbitMQ需要再次投递消息。

        reject:消息处理失败并拒绝该消息,RabbitMQ 从队列中删除该消息。

        而 Spring AMQP 已经实现了消费者的消息确认功能,并且可以通过配置消费者的配置文件来选择三种不同的处理方式。

        none:不处理,即消息投递该消费者后立刻 ack,消息会立刻从 MQ 删除。(不建议)

        manua:手动模式。需要自己在业务代码中调用 api,发送 ack reject,存在业务入侵,但更加灵活。

        auto:自动模式。Spring AMQP 利用 AOP 对我们的消息处理逻辑做了增强循环,当业务正常执行时则自动返回 ack,当业务出现异常时,根据异常判断返回不同结果。

        如果是业务异常,会自动返回 nack;如果是消息处理或校验异常,自动返回 reject

spring:
  rabbitmq:
    listener:
      simple:
        ackonwledge-mode: auto # 配置为 auto 模式

3.2.失败重试机制

        Spring AMQP 提供了消费者重试机制,在消费者出现异常时利用本地重试,而不是无限的发送消息到 MQ 中,我们可以通过在 yml 配置文件中添加相关配置来开启重试机制。

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true      # 开启重试机制
          initial-interval: 1000ms # 第一次重试间隔时间
          multiplier: 1      # 失败后重试间隔倍数
          max-attempts: 3    # 最大重试次数
          stateless: true    # true无状态;false有状态。如果业务中包含事务,则设置为false

        这些都是配置的参数都是默认值,可根据需求进行修改。而在开启重试模式后,如果耗尽重试次数后消息仍然失败,就会使用 MessageRecoverer 接口来处理,它包含三种实现。

1.RejectAndDontRequeueRecoverer:重试次数耗尽后,直接 reject,丢弃消息。(默认

2.ImmediateRequeueMessageRecoverer:重试耗尽后,返回 nack,消息重新入队。

3.RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机。

        3.1.定义接收失败消息的交换机、队列及其绑定关系。

        3.2.定义 RepublishMessageRecoverer 为一个 Bean

3.3.业务幂等性

        在程序开发中,幂等是指同一个业务,执行一次或多次对业务状态的影响是一致的

保证幂等性的方案:

        1.每一个消息都设置一个唯一 Id,利用 Id 区分是否是重复复消息:

        每一条消息都生成一个唯一 Id,随消息一起投递。消费者接收到消息后去处理业务,成功后将 Id 储存在数据库中,如果下次收到相同消息,利用数据库中的 Id 进行重复判断,若重复则放弃业务处理。

        而在消息转化器的声明中,我们可以配置自动生成创建消息 Id,以此来实现业务幂等性。

可以看到消息成功带上了 Id

        如果想要在 Java 客户端中获取到消息,则可以将数据的接收类型改为 Message 类型,getbody 方法获取消息体,getMessageId 方法获取消息 Id

        2.结合业务逻辑,基于业务本身做判断。(略)


4.延迟消息

        发送者可以在发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间之后才会收到消息。可以通过延时消息实现延时任务功能,设置在一定时间之后才执行任务

4.1.死信交换机

        当一个队列中的消息满足下列情况之一时,就会成为死信(dead letter):                         

  • 消费者使用 basic.reject basic.nack 声明消费失败,并且消息的 requeue 参数设置为 false           
  • 消息是一个过期消息(达到了队列或消息本身设置的过期时间),超时无人消费                         
  • 要投递的队列消息堆积满了,最早的消息可能成为死信

        如果队列通过 dead-letter-exchange 属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中。这个交换机称为死信交换机(Dead Letter Exchange,简称 DLX),我们可以利用该机制实现延迟消息。       

        生成相应的交换机及队列并绑定关系。

         使用 setExpiration 方法可以设置消息的过期时间。(单位 ms

4.2.延迟消息插件

        DelayExchange 插件可以将普通的交换机改造为支持延迟消息功能的交换机,当消息投递到交换机后可以暂存一定时间,到期后再投递到队列。

        因为 RabbitMQ 是通过 docker 进行安装的,所以只需将下载好的文件移动至当时配置好的 mq 数据卷位置即可。

可通过 docker volume inspect xxx 命令查看对应的数据卷

docker exec -it mq xxx enable rabbitmq_delayed_message_exchange 安装

方式1:@Exchange 中添加 delayed 并设置开启。

方式2:在构造交换机时后跟 .delayed() 开启延时。 

        最后在发送消息时添加上 setDelay 方法设置延时时间。(单位 ms
 


【~~完结~~】

如有不足,请指出,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值