RabbitMQ

文章目录

  • [1. RabbitMQ核心概念](#1. RabbitMQ核心概念)
    • [1.1 生产者Producer和消费者Conumer](#1.1 生产者Producer和消费者Conumer)
    • [1.2 Connection和Channel](#1.2 Connection和Channel)
    • [1.3 Vrtual host](#1.3 Vrtual host)
    • [1.4 Queue](#1.4 Queue)
    • [1.5 Exchange](#1.5 Exchange)
      • [1.5.1 Rabbit MQ交换机的四种类型](#1.5.1 Rabbit MQ交换机的四种类型)
      • [1.5.2 Routing Key 和 Biding Key](#1.5.2 Routing Key 和 Biding Key)
    • [1.6 AMQP协议](#1.6 AMQP协议)
  • [2. Rabbit MQ七种工作模式](#2. Rabbit MQ七种工作模式)
    • [2.1 Simple(简单工作模式)](#2.1 Simple(简单工作模式))
    • [2.2 Work Queue(工作队列)](#2.2 Work Queue(工作队列))
    • [2.3 Publish/Subscribe(发布/订阅模式)](#2.3 Publish/Subscribe(发布/订阅模式))
    • [2.4 Routing(路由模式)](#2.4 Routing(路由模式))
    • [2.5 Topics(通配符模式)](#2.5 Topics(通配符模式))
    • [2.6 RPC(RPC通信模式)](#2.6 RPC(RPC通信模式))
    • [2.7 Publisher Confirms(发布确认模式--异步)](#2.7 Publisher Confirms(发布确认模式--异步))
  • [3. Rabbit MQ 高级特性](#3. Rabbit MQ 高级特性)
    • [3.1 消息确认](#3.1 消息确认)
      • [3.1.1 自动确认](#3.1.1 自动确认)
      • [3.1.2 手动确认](#3.1.2 手动确认)
      • [3.1.3 手动确认的三种方式(Rabbit MQ)](#3.1.3 手动确认的三种方式(Rabbit MQ))
        • [1. 肯定确认](#1. 肯定确认)
        • [2. 否定确认--单个](#2. 否定确认--单个)
        • [3. 否定确认--批量](#3. 否定确认--批量)
      • [3.1.3 消息确认的三种方式(Spring-AMQP)](#3.1.3 消息确认的三种方式(Spring-AMQP))
    • [3.2 持久性(可靠性保证机制)](#3.2 持久性(可靠性保证机制))
      • [3.2.1 交换机持久化(默认是持久的)](#3.2.1 交换机持久化(默认是持久的))
      • [3.2.2 队列持久化(默认是持久化的)](#3.2.2 队列持久化(默认是持久化的))
      • [3.2.3 消息持久化](#3.2.3 消息持久化)
      • [3.2.4 总结](#3.2.4 总结)
    • [3.3 发送方确认(可靠性保证机制)](#3.3 发送方确认(可靠性保证机制))
      • [3.3.1 confirm确认模式](#3.3.1 confirm确认模式)
      • [3.3.2 return退回模式](#3.3.2 return退回模式)
    • [3.4 重试机制(在自动确认下才生效)](#3.4 重试机制(在自动确认下才生效))
    • [3.5 TTL](#3.5 TTL)
      • [3.5.1 设置消息的TTL & 设置队列的TTL](#3.5.1 设置消息的TTL & 设置队列的TTL)
      • [3.5.2 两者区别](#3.5.2 两者区别)
    • [3.6 死信队列](#3.6 死信队列)
      • [3.6.1 死信的概念](#3.6.1 死信的概念)
    • [3.7 延迟队列](#3.7 延迟队列)
      • [3.7.1 延迟的概念](#3.7.1 延迟的概念)
      • [3.7.2 应用场景](#3.7.2 应用场景)
      • [3.7.3 TTL + 死信队列](#3.7.3 TTL + 死信队列)
      • [3.7.4 延迟队列插件](#3.7.4 延迟队列插件)
    • [3.8 事务](#3.8 事务)
    • [3.9 消息分发](#3.9 消息分发)
      • [3.9.1 概念](#3.9.1 概念)
      • [3.9.2 应用场景](#3.9.2 应用场景)
        • [3.9.2.1 限流](#3.9.2.1 限流)
        • [3.9.2.2 负载均衡](#3.9.2.2 负载均衡)
  • [4. Rabbit MQ 应用问题](#4. Rabbit MQ 应用问题)
    • [4.1 幂等性保障](#4.1 幂等性保障)
      • [4.1.1 MQ的幂等性](#4.1.1 MQ的幂等性)
    • [4.2 顺序性保障](#4.2 顺序性保障)
      • [4.2.1 可能打破RabbitMQ的顺序性的场景](#4.2.1 可能打破RabbitMQ的顺序性的场景)
      • [4.2.2 顺序性保障方案](#4.2.2 顺序性保障方案)
    • [4.3 消息积压问题](#4.3 消息积压问题)
      • [4.3.1 消息积压原因](#4.3.1 消息积压原因)
      • [4.3.2 解决方案](#4.3.2 解决方案)

1. RabbitMQ核心概念

RabbitMQ 是一个消息中间件,也是一个生产者消费者模型,它负责接收、存储并转发消息。

1.1 生产者Producer和消费者Conumer

**Producer: ** 消息的生产者,是RabbitMQ Server的客户端,向RabbitMQ发送消息。

**Conumer: ** 消息的消费者,是RabbitMQ Server的客户端,从RabbitMQ中接收消息。

1.2 Connection和Channel

Connection: ** 连接。是客户端和Rabbit MQ 服务器之间的一个TCP连接**,这个连接时建立消息传递的基础,它负责传输客户端和服务器之间的所有数据和控制信息。

**Channel: ** 信道、通道。Channel是在Connection之上的抽象层,在Rabbit MQ中,一个TCP连接中,即一个Connection中可以有多个Channel,每个Channel都是独立的虚拟连接,消息的发送和接收都是基于Channel的。

channel的主要作用是将消息的读写操作复用到同一个TCP连接上,这样就可以减少建立和关闭连接的开销,从而提高性能。

1.3 Vrtual host

Vrtual host是虚拟主机 ,这是一个虚拟概念。它为消息队列提供了一种逻辑上的隔离机制。对于Rabbit MQ而言,一个Broker Server上可以存在多个Vrtual host。

当多个不同的⽤⼾使⽤同⼀个 RabbitMQ Server 提供的服务时,可以虚拟划分出多个 vhost,每个⽤⼾在⾃⼰的 vhost 创建exchange/queue 等 。类似于MySQL中数据库的概念。

1.4 Queue

Queue是队列 。是RabbitMQ的内部对象,用于存储消息。

一个队列可以被多个消费者订阅。一个消费者可以订阅多个队列。

1.5 Exchange

Exchange是交换机 。 是消息到达Broker的第一站,它负责接收生产者发送到RabbitMQ的消息,并根据特定的规则把这些消息路由到一个或多个队列中。

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

1.5.1 Rabbit MQ交换机的四种类型

不同的交换机类型就对应着不同的工作模式。

  1. **Fanout: ** 广播。将消息交给所有绑定到交换机的队列(发布/订阅模式)。
  2. Dirext: 顶向。将消息交给符合指定routing key的队列(routing模式)。
  3. Topic: 通配符。将消息交给符合routing pattern的队列(通配符模式)。
  4. **Headers: ** headers类型的交换机不依赖于路由的匹配规则来路由消息,而是根据发送消息内容中的headers属性来进行匹配。这种交换机的性能很差,也不实用。

1.5.2 Routing Key 和 Biding Key

Routing Key:路由键。生产者将消息发送给交换机时,指定的一个字符串,⽤来告诉交换机应该如何处理这个消息.

Biding Key:绑定键. RabbitMQ中通过Binding(绑定)将交换器与队列关联起来, 在绑定的时候⼀般会指定⼀个Binding Key, 这样RabbitMQ就知道如何正确地将消息路由到队列了.

生产者在发送消息的时候会携带一个Routing Key,交换机就会将消息发送给 Routing Key 和 Binding Key 能匹配上的队列。

一个队列和交换机之间可以有多个Binding Key。

1.6 AMQP协议

AMQP(Advanced Message Queuing Protocol)是⼀种⾼级消息队列协议, AMQP定义了⼀套确定的消息交换功能, 包括交换器(Exchange), 队列(Queue) 等. 这些组件共同⼯作, 使得⽣产者能够将消息发送到交换器. 然后由队列接收并等待消费者接收. AMQP还定义了⼀个⽹络协议, 允许客⼾端应⽤通过该协议与消息代理和AMQP模型进⾏交互通信。

RabbitMQ是AMQP协议的实现。

2. Rabbit MQ七种工作模式

2.1 Simple(简单工作模式)

  1. 一个生产者,一个消费者。
  2. 消息只能被消费一次。也称为点对点模式
  3. 适用场景:消息只能被单个消费者处理。

2.2 Work Queue(工作队列)

  1. 一个生产者,多个消费者。
  2. 消息只有一份,多个消费者共同消费所有的消息,每条消息不会被重复消费
  3. 适用场景:集群环境中做异步处理。

2.3 Publish/Subscribe(发布/订阅模式)

与上面两种工作模式相比,发布订阅模式的图中多了交换机的概念。并不说上面的两种工作模式中没有交换机,其实是有的。在Rabbit MQ中发布消息一定会经过交换机。

只不过与前两种工作模式相比,发布订阅模式中的交换机起到的作用不同:

  1. 前两种工作模式:消息只有一份,交换机起到消息转发的作用。

  2. 发布订阅模式:交换机会将消息转发给所有绑定到交换机的队列。消息不再是只有一份。

  3. 一个生产者多个消费者。

  4. 交换机将消息复制多份,每个消费者接收相同的消息。

  5. 适用场景:消息需要被多个消费者同时接收的场景。

2.4 Routing(路由模式)

  1. 路由模式是发布订阅模式的变种, 在发布订阅基础上, 增加路由key。
  2. 发布订阅模式是⽆条件的将所有消息分发给所有消费者, 路由模式是Exchange根据RoutingKey的规则,将数据筛选后发给对应的消费者队列。
  3. 适合场景: 需要根据特定规则分发消息的场景.

2.5 Topics(通配符模式)

  1. 路由模式的升级版, 在routingKey的基础上,增加了通配符的功能.
  2. Topics和Routing的基本原理相同,即:⽣产者将消息发给交换机,交换机根据RoutingKey将消息转发给与RoutingKey匹配的队列. 类似于正则表达式的⽅式来定义Routingkey的模式.
  3. 适合场景: 需要灵活匹配和过滤消息的场景.

2.6 RPC(RPC通信模式)

RPC:远程过程调用。

在PRC通信的过程中,没有生产者和消费者。RabbitMQ 实现RPC通信的过程,大概就是通过两个队列实现一个可回调的过程。

  1. 客户端发送一个消息到指定队列,并在消息属性中设置replyTo字段,这个字段指定了一个回调队列,服务端处理后,会把响应结果发送到这个队列;消息中还会携带一个correlationId,。
  2. 服务端接收到请求之后,处理请求并发送响应消息到replyTo指定的回调队列中,与此同时,这条消息中依旧携带着correlationId。
  3. 客户端在回调队列上等待响应消息,一旦收到响应,客户端会检查消息的correlationId属性,以确保它是所期望的响应。

2.7 Publisher Confirms(发布确认模式--异步)

发布确认模式是Rabbit MQ消息可靠性保证的机制。发布确认有三种策略:

  1. 单独确认策略:每一条消息都单独进行确认。

  2. 批量确认策略:消息达到指定数量后进行确认。

  3. 异步确认策略(性能最好):生产者可以同时发布消息和等待信道返回确认消息。

  1. 在这种模式下,需要生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上发布的每一条消息都会被指派一个唯一的ID(ID以信道为边界,从1开始),生产者可以将这些序列号与消息关联起来,以便跟踪消息的状态。

  2. 当消息被RabbitMQ服务器接收并处理后,服务器会异步地向生产者发送一个ACK(包含消息的唯一ID)给生产者,表示消息已经送达。如果消息和队列是持久化的,那么确认消息会在将消息写入磁盘之后发出。

  3. 当消息最终得到确认之后没生产者可以通过回调方法来处理该确认消息;如果发生错误导致消息丢失,就会发出nack命令,生产者同样可以在回调方法中处理该nack命令。

  4. 适用场景:对数据安全性要求比较高的场景。

发布确认模式下,生产者可以确保消息被Rabbit MQ Server成功接收,从而避免消息丢失 ;但是并不能确保消息会被消费者正常消费

3. Rabbit MQ 高级特性

3.1 消息确认

RabbitMQ向消费者发送消息之后,就会把这条消息删除,这样就可能会造成消息丢失。消息确认机制是为了保证消息从队列可靠的到达消费者。

消费者在订阅队列时,可以只当autoAck参参数进行设置,消息确认机制分为自动确认和手动确认两种。

3.1.1 自动确认

当autoAck等于true时,Rabbit MQ会自动把发送出去的消息置为确认,然后从内存/磁盘中删除该消息。只要消息到达消费者就会自动确认,不管消费者是否真正地消费到了这些消息。

3.1.2 手动确认

当autoAck等于false时,Rabbit MQ会等待消费者显式地调用Basic.Ack命令,回复确认信号后才会从内存/磁盘中删除该消息。

此时,队列中的消息有两种:1)等待投递的消息;2)已经投递,等待消费者确认的消息。如果Rabbit MQ一直没有收到消息的确认信号,并且消费该消息的消费者已经断开连接,Rabbit MQ会安排该消息重新进入队列,等待投递给下一个消费者。

3.1.3 手动确认的三种方式(Rabbit MQ)

1. 肯定确认

1. Channel.basicAck(long deliveryTag, boolean multiple)

  1. deliveryTag: 消息的唯一标识,它是一个单调递增的64位的长整型值。deliveryTag是每个通道(Channel)独立维护的,所以在每个通道上都是唯一的,当消费者确认一条消息时,必须使用对应的通道上进行确认。
  2. multiple: 是否批量确认。在某些情况下,为了减少网络流量,可以对一系列连续的deliveryTag进行批量确认。值为true则会一次性ack所有小于或等于指定deliveryTag的消息。值为false,则会确认当前指定deliveryTag的消息。
2. 否定确认--单个

Channel.basicReject(long deliveryTag, boolean requeue)
requeue: 表示拒绝后,这条消息如何让处理。requeue=true表示Rabbit MQ会重新把这条消息存入队列,以便可以发送给下一个订阅的消费者;requeue=false,Rabbit MQ会把消息从队列中移除,不会把它发送给新的消费者。

3. 否定确认--批量

Channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)

Basic.reject命令:一次只能拒绝一条消息。

Basic.Nack命令:可以批量拒绝消息。

3.1.3 消息确认的三种方式(Spring-AMQP)

Ackmowledge.NONE

对应Rabbit MQ中的自动确认。

在这种模式下,消息一旦被投递给消费者,无论消费者是否正确处理了该消息,Rabbit MQ都会自动确认该消息,并把该消息从队列中移除,有消息丢失的风险。

Ackmowledge.AUTO

只有在消息处理成功之后自动确认消息,如果处理过程中抛出了异常,则不会确认消息。

当消费者出现异常的时候,Rabbit MQ会不断的进行重发,如果一直失败,消息无法被确认,也无法nack,就会导致消息积压。

Ackmowledge.MANUAL

对应RabbitMQ中的手动确认。

手动确认模式下,消费者必须在成功处理消息后显式调用basicAck方法来确认消息。如果消息未被确认,Rabbit MQ会认为消息未被成功处理,并且会在消费者可用是重新投递该消息。

这种模式提高了消息处理的可靠性,因为即使消费者处理消息失败,消息也不会丢失,而是可以被重新处理。

3.2 持久性(可靠性保证机制)

持久性可以保证Rabbit MQ退出或者由于某种原因崩溃时,保证消息不丢失。

3.2.1 交换机持久化(默认是持久的)

交换机的持久化相当于将交换机的属性在服务器内部保存,当MQ的服务器发生意外或者关闭之后,重启Rabbit MQ时不需要去重新建立交换机,交换机会自动建立,相当于一直存在(嚯,免死金牌🤓)。

相反如果交换机不设置持久化,那么在Rabbit MQ服务重启之后,相关的交换机原数据会丢失,对一个长期使用的交换机来说,建议将其置为持久化的。

java 复制代码
ExchangeBuilder.topicExchange(Constant.ACK_EXCHANGE_NAME).durable(true).build()

3.2.2 队列持久化(默认是持久化的)

如果不设置队列的持久化,那么在Rabbit MQ服务重启之后,该队列就会被删除掉,此时数据也会丢失。

设置了队列的持久化能够保证队列本身的原数据不会因为异常情况而丢失,但是并不能保证内部所存储的消息不会丢失。

要想保证消息不丢失,还需要将消息设置为持久化。

java 复制代码
QueueBuilder.durable(Constant.ACK_QUEUE).build();

3.2.3 消息持久化

由于消息是存储在队列中的:

  1. 队列持久化 + 消息持久化 = 消息不丢失。
  2. 队列持久化 + 消息不持久化 = 消息丢失。
  3. 队列不持久化 + 消息持久化 = 消息丢失。

3.2.4 总结

如果将所有的消息都设置为持久化,会严重的影响到Rabbit MQ的性能。写入磁盘的速度比写入内存的速度慢的很多。所以出于对性能的考虑,对于可靠性不是那么高的消息可以不采取持久化处理来提高整体的吞吐量。在选择是否要将消息持久化的时候,需要在可靠性和吞吐量之间做一个权衡。

将交换机、队列、消息都设置成持久化之后就能百分百保证数据不丢失吗?

并不是。

  1. 从消费者来说,如果在订阅消费队列时将autoAck参数设置为true,那么消费者接收到相关消息之后,还没来得及处理该消息就宕机了,这样也算是数据丢失。想要解决这种情况就需要设置手动确认。

  2. 在持久化的消息正确存入Rabbit MQ之后,还需要有一段事件才能存入磁盘中。Rabbit MQ并不会为每条消息都进行同步存盘处理,可能仅仅保存到操作系统缓存中而不是物理磁盘中。如果在这段时间内Rabbit MQ服务几点发生了宕机、重启等异常情况,消息保存还没来得及落盘,那么这些消息将会丢失。

3.3 发送方确认(可靠性保证机制)

发送方确认是保证消息正确到达服务器的。

Rabbit MQ提供了两种方式来控制消息的可靠性投递:

  1. confirm确认模式。

  2. return退回模式。

注意: 这两种方式不互斥,可以单独使用,也可以结合使用。

3.3.1 confirm确认模式

Producer在发送消息的时候,对发送端设置一个ConfirmCallback的监听,无论消息是否到达交换机,这个监听都会被执行,如果交换机成功收到,ACK确认为true,如果没有收到消息,ACK就为false。

RabbitTemplate.ConfirmCallback 和 ConfirmListener 的区别?

在Rabbit MQ中,ConfirmListener 和 ConfirmCallback 都是用来处理消息确认的机制,但它们属于不同的客户端库,并且使用的场景和方式有所不同。

  1. ConfirmListener 是Rabbit MQ Java Client 库中的接口。这个库是Rabbit MQ官方提供的一个直接与Rabbit MQ服务器交互的客户端库。ConfirmListener 接口提供了两个方法:handleAck 和 handleNack,用于处理消息确认和否认确认的事件。

  2. ConfirmCallback 是 Spring AMQP 框架中的一个接口。专门为Spring环境设计,用于简化与Rabbit MQ交互的过程。它只包含一个confirm方法,⽤于处理消息确认的回调.

在 Spring Boot 应⽤中, 通常会使⽤ ConfirmCallback, 因为它与 Spring 框架的其他部分更加整合, 可以利⽤ Spring 的配置和依赖注⼊功能. ⽽在使⽤ RabbitMQ Java Client 库时, 则可能会直接实现 ConfirmListener 接⼝, 更直接的与RabbitMQ的Channel交互 。

3.3.2 return退回模式

消息到达交换机之后,会根据路由规则匹配,把消息放入Queue中。消息从交换机到队列的过程中,如果一条消息无法被任何队列消费,可以选择把消息退回给发送者。消息退回给发送者时,可以设置一个回到方法,对被退回的消息进行处理。

3.4 重试机制(在自动确认下才生效)

在消息传递过程中,可能会遇到各种问题,这些问题可能会导致消息处理失败,为了解决这些问题,Rabbit MQ提供了重试机制,允许消息在处理失败后重新发送。

1. 自动确认模式下: 程序逻辑异常,多次重试还是失败,消息就会被自动确认,那么消息就会丢失了。

2. 手动确认模式下: 程序逻辑异常,多次重试消息依然处理失败,无法被确认,就一直时unacked的状态,导致消息积压。

3.5 TTL

TTL:过期时间。

当消息到达存活时间之后,如果还没有被消费,就会被自动清除掉。

3.5.1 设置消息的TTL & 设置队列的TTL

两种方法设置消息的TTL:

  1. 设置队列的TTL。队列中所有消息都有相同的过期时间。

  2. 对消息本身进行单独的设置,每条消息的TTL可以不同。

如果两种方法一起使用,消息的TTL以两者之间较小的那个TTL为准。

3.5.2 两者区别

设置队列的TTL,一旦消息过期,就会从队列中删除。

设置消息的TTL,即使消息过期,也不会马上从队列中删除,而是在即将投递到消费者之前进行判定的。

为什么这两种方法处理的方式不一样?

因为设置队列过期时间,队列中已过期的消息肯定在队列头部,Rabbit MQ只要定期从对头开始扫描是否有过期的消息即可。

而设置消息的TTL的方式,每条消息的TTL不同,如果要删除所有过期消息需要扫描整个队列,所以不如等到此消息即将被消费时再判定是否过期,如果过期再进行删除即可。

3.6 死信队列

3.6.1 死信的概念

死信:由于种种原因,无法被消费的消息,就是死信。

当消息在一个队列中变成死信之后,它能重新被发送到另一个交换机中,这个交换器就是DLX,绑定DLX的队列,就称为死信队列。

消息变成死信的几种情况:

  1. 消息被拒绝并且不能重新进入队列。

  2. 消息过期。

  3. 队列达到最大长度。

3.7 延迟队列

3.7.1 延迟的概念

延迟队列:在消息被发送之后,并不想让消费者立刻拿到消息,而是等待特定的时间之后,消费者才能拿到这个消息进行消费。

3.7.2 应用场景

  1. 智能家居:用户恶意通过手机远程遥控家里的智能设备在指定的时间进行工作,这个时候就可以将用户的指令发送到延迟队列,当指令预设的时间到了再将指令推送到智能设备。
  2. 日常管理:预定会议,需要在会议开始的前15分钟提醒人参加会议。
  3. ...

Rabbit MQ本身并不直接支持延迟队列的功能,但是可以通过TTL+死信队列的方式组合模拟出延迟队列的功能。

假设一个应用中需要将每条消息都设置为10s的延迟,生产者通过normal_exchange这个交换机将发送的消息存储在norimal_queque这个队列中。消费者订阅的并非是normal_queque这个队列,而是dlx_queque这个队列,当消息从normal_queque这个队列中过期之后被存入dlx_queue这个队列中,消费者就恰巧消费到了延迟10s的这条消息。

3.7.3 TTL + 死信队列

假设,我们现在发送了两条消息,第一条的TTL为30s,第二条的TTL为10s。

这时,我们就会发现一个问题,第二条消息并不是在第10s进入死信队列的,而是延后了20s,和第一天消息一起进入死信队列的。

由于队列是先进先出,Rabbit MQ指挥检查队首的消息是否过期。如果过期则会将消息丢到死信队列中。所以就导致,第二条消息在过期的时候Rabbit MQ并不知道,直到第一条消息被Rabbit MQ检测到过期了。才会检测第二条消息,所以就导致第二条消息进入死信队列的时间延后了20s.

所以在考虑使用TTL和死信队列实现延迟队列的时候,需要确认业务上每个任务的延迟时间是一致的,入股遇到不同任务类型需要不同的延迟的话,需要为每一种不同延迟时间的小欧希建立单独的消息队列。

3.7.4 延迟队列插件

延迟队列插件时RabbitMQ提供的,实现了延迟的功能。

使用延迟插件实现的延迟队列解决了上面同一个队列中消息的延迟时间不同的问题。

3.8 事务

Rabbit MQ 是基于AMQP协议实现的,该协议实现了事务机制,因此Rabbit MQ也支持事务机制。

SpringAMQP也提供了事务的相关操作,Rabbit MQ 事务允许开发者确保消息的发送和接收时原子性的,要么全部成功,要么全部失败。比如:一次发送了两条消息,这两条消息要么全部发送成功,要么全部发送失败。

3.9 消息分发

3.9.1 概念

Rabbit MQ队列拥有多个消费者时,队列会把收到的消息分派给不同的消费者,每条消息指挥发送给订阅者列表中的一个消费者。

默认情况下,RabbitMQ是以轮询的方式进行分发的,但是每个消费者的消费能力不同,如果某些消费者的消费速度很慢,而某些消费者消费速度快,就会导致消息积压。

基于这种情况,我们就可以使用channel.basicQos方法来限制当前信道上的消费者所能保持的最大未确认消息的数量。比如:消费端调用channelbasicQos(5),Rabbit MQ会为该消费者计数,发送一条消息计数 +1 ,消费一条消息计数-1,当达到设定的上限,Rabbit MQ就不会再给这个消费者发送消息,知道消费者确认了某条消息。

3.9.2 应用场景

消息分发是常见场景:

  1. 限流
  2. 非公平分发
3.9.2.1 限流

比如,正常情况下订单系统每秒最多处理5000个请求,是可以满足需求的。

但是如果是在秒杀的时间点,请求量瞬间增多,如果这些请求全部通过MQ发送到订单系统,无疑会把订单系统压垮。

使用RabbitMQ的限流机制,就可以控制消费端一次只拉取N个请求。

限流机制:

  1. 消息应答方式设置为手动应答。

  2. 设置prefetchCount参数

3.9.2.2 负载均衡

根据消费者处理任务的快慢,进行分派任务。

prefetch=1表示:Rabbit MQ一次只给消费者一条消息,该消费者在处理并确认一条消息之前,不要向该消费者发送新的消息。

4. Rabbit MQ 应用问题

4.1 幂等性保障

幂等性指的是对资源的影响,而不是返回结果

应用上的幂等性:比如说是一个支付的接口,多次调用同一个订单的支付接口,对于系统的影响是一样的,只能扣一次款。

4.1.1 MQ的幂等性

同一条消息,多次消费,对于系统的影响是相同的。

一般消息中间件的消息传输保障分为三个层级。

  1. At most once:最多一次.消息可能会丢失,但绝不会重复传输.
  2. At least once:最少一次.消息绝不会丢失,但可能会重复传输.
  3. Exactly once:恰好一次.每条消息肯定会被传输一次且仅传输一次.

RabbitMQ支持"最多一次"和"最少一次"。

对于"恰好一次",目前RabbitMQ还做不到,不仅是RabbitMQ,目前市面上主流的消息中间件,都做不到这一点。

在业务使用中,对于可靠性要求比较高的场景,建议使用"最少一次",以防止消息丢失。"最多一次"会因为消息发送过程中,网络问题,消费出现异常等种种原因,导致消息丢失.

但是"最少一次"就会造成一个问题,消费端会收到重复的消息,也会造成对同一条消息进行多次处理。一些不重要的业务还好一点,对于重要的业务,如果不对重复的消息进行处理,会造成严重事故.

4.2 顺序性保障

消息的顺序性是指消费者消费的消息和生产者发送消息的顺序是一致的。

在不考虑消息丢失、网络故障等异常的情况下,如果只有一个消费者,最好也只有一个生产者的情况下,是可以保证消息的顺序性的,如果有多个生产者同时发送消息,无法确定消息到达RabbbitMQ Broker的前后顺序,也就无法验证消息的顺序性。

4.2.1 可能打破RabbitMQ的顺序性的场景

  1. 多个消费者:当队列配置了多个消费者时,消息可能会被不同的消费者并行处理,从而导致消息处理的顺序性无法保证。
  2. 网络波动或异常:在消息传递过程中,如果出现网络波动或异常,可能会导致消息确认(ACK) 丢失,从而使得消息被重新入队和重新消费,造成顺序性问题。
  3. 消息重试:如果消费者在处理消息后未能及时发送确认,或者确认消息在传输过程中丢失,那么MQ可能会认为消息未被成功消费而进行重试,这也可能导致消息处理的顺序性问题。
  4. 消息路由问题:在复杂的路由场景中,消息可能会根据路由键被发送到不同的队列,从而无法保证全局的顺序性。
  5. 死信队列:消息因为某些原因(如消费端拒绝消息)被放入死信队列,死信队列被消费时,无法保证消息的顺序和生产者发送消息的顺序一致。

4.2.2 顺序性保障方案

消息顺序性保障分为:局部顺序性保证和全局顺序性保证.

局部顺序性通常指的是在单个队列内部保证消息的顺序;全局顺序性是指在多个队列或多个消费者之间保证消息的顺序.

在实际应用中,全局顺序性很难实现,可以考虑使用业务逻辑来保证顺序性,比如在消息中嵌入序列号,并在消费端进行排序处理.相对而言,局部顺序性更常见,也更容易实现.

RabbitMQ作为一个分布式消息队列,主要优化的是吞吐量和可用性,而不是严格的顺序性保证.如果业务场景确实需要严格的消息顺序,可能需要在应用层面进行额外的设计和实现.

消息的顺序性保证的常见策略.

  1. 单队列单消费者

    最简单的方法是使用单个队列,并由单个消费者进行处理.同一个队列中的消息是先进先出的,这是RabbitMQ来帮助我们保证的.

  2. 分区消费

    单个消费者的吞吐太低了,当需要多个消费者以提高处理速度时,可以使用分区消费.把一个队列分割成多个分区,每个分区由一个消费者处理,以此来保持每个分区内消息的顺序性.

  3. 消息确认机制

    使用手动消息确认机制,消费者在处理完一条消息后,显式地发送确认,这样RabbitMQ才会移除并继续发送下一条消息.

  4. 业务逻辑控制

    在某些情况下,即使消息乱序到达,也可以在业务逻辑层面实现顺序控制.比如通过在消息中嵌入序列号,并在消费时根据这些信息来处理.

RabbitMQ本身并不保证全局的严格顺序性,特别是在分布式系统中.在实际应用开发中,根据具体的业务需求,可能需要结合多种策略来实现所需要的顺序保证.

4.3 消息积压问题

4.3.1 消息积压原因

消息队列中,待处理的消息数量超过了消费者处理的能力,导致消息在队列中出现不断堆积的现象,就是消息积压。

原因:

1)消息生产过快:在高流量或者高负载的情况下,生产者以极高的速率发送消息,超过了消费者的处理能力。

2)消费者处理消息的能力不足:消费者处理消息的速度跟不上消息生产的速度,也会导致消息在队列中积压。

可能的原因:

  1. 消费端业务逻辑复杂,耗时长。
  2. 消费端代码性能低。
  3. 系统资源限制,如CPU、内存、磁盘IO等也会限制消费者处理消息的效率。
  4. 异常消息处理不当。消费者在处理消息时出现异常,导致消息无法被正确处理和确认。

3)网络问题:因为网络延迟或者网络不稳定,消费者无法及时接收或确认消息,最终导致消息积压。

4)RabbitMQ服务器配置较低。

4.3.2 解决方案

  1. 提高消费者效率

    1)增加消费者实例数量。

    2)优化业务逻辑,比如,引入多线程来处理业务。

    3)设置prefetchCount,当⼀个消费者阻塞时,消息转发到其他未阻塞的消费者。

    4)消息发⽣异常时,设置合适的重试策略,或者转⼊到死信队列。

  2. 限制生产者速率。比如,流量控制、限流算法等。

    1)流量控制:在消息⽣产者中实现流量控制逻辑,根据消费者处理能⼒动态调整发送速率。

    2)限流:使⽤限流⼯具,为消息发送速率设置⼀个上限。

    3)设置过期时间。如果消息过期未消费,可以配置死信队列,以避免消息丢失,并减少对主队列的压力。

  3. 资源与配置优化。⽐如升级RabbitMQ服务器的硬件,调整RabbitMQ的配置参数等。

相关推荐
初次攀爬者10 小时前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者2 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧3 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖4 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农4 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者4 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀4 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Ronin3054 天前
信道管理模块和异步线程模块
开发语言·c++·rabbitmq·异步线程·信道管理
Asher05094 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式
凉凉的知识库4 天前
Go中的零值与空值,你搞懂了么?
分布式·面试·go