当前位置: 首页 > news >正文

RabbitMQ--基础--02--原理

RabbitMQ–基础–02–原理


1、架构

1.1、整体架构

  1. 整体架构上是一个生产者与消费者模型
  2. 主要功能:负责接收、存储、转发消息

在这里插入图片描述

1.2、核心架构

在这里插入图片描述

1.2.1、Broker(服务节点)

  1. 是rabbitmq的server节点
  2. 功能:接受客户端的连接

1.2.1.1、消费数据的流程

  1. 生产者将消息存入 RabbitMQ 中的 Broker
  2. 消费者从 Broker 中消费数据

在这里插入图片描述

1.2.2、Virtual Host(虚拟服务器,简称为vhost)

  1. 一个Broker中可以有多个vhost
  2. 每一个vhost本质上是一个独立的小型RabbitMQ服务器,拥有自己独立的队列、交换器及绑定关系等,并且它拥有自己独立的权限
  3. vhost 就像是虚拟机与物理服务器一样,它们在各个实例间提供逻辑上的分离,为不同程序安全保密地运行数据,它既能将同一个RabbitMQ 中的众多客户区分开,又可以避免队列和交换器等命名冲突
  4. vhost之间是绝对隔离的,无法将vhost1中的交换器与vhost2中的队列进行绑定,这样既保证了安全性,又可以确保可移植性。如果在使用RabbitMQ达到一定规模的时候,建议用户对业务功能、场景进行归类区分,并为之分配独立的vhost。
  5. 类比:
    1. RabbitMq 相当于 MySQL,
    2. vhost 相当于 MySQL 中的一个数据库

在这里插入图片描述

1.2.3、Connection(连接)

  1. 用于应用程序生产者/消费者与 Broker 的 网络TCP/IP连接
  2. Connection可以有多个

1.2.4、Channel(网络信道)

  1. 多路复用连接中的一条独立的双向数据流通道
  2. 几乎所有的操作都在 Channel 中进行
  3. Channel 是进行消息读写的通道,每个通道 Channel 代表一个会话任务
  4. 信道是建立在真实的TCP连接内地虚拟链接,AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成的
  5. 作用:为了复用一条TCP连接

1.2.5、Exchange(交换机)

  1. 接收并转发消息
  2. 不具备储存消息的能力
  3. 生产者将消息发送到交换机 Exchange,由交换机 Exchange 将消息路由到一个或者多个队列中。
    1. 如果路由不到
      1. 或许会返回给生产者
      2. 或许会直接丢弃。
  4. RabbitMQ 中的交换机有四种类型
  5. Exchange一定是要有的,如果没写的话,则是会使用默认的交换机。

在这里插入图片描述

1.2.6、RoutingKey(路由键)

  1. 交换机可以用它来确定如何路由一个特定消息。
  2. 生产者将消息发给交换机的时候,一般会指定一个 RoutingKey,用来指定这个消息的路由规则
    1. RoutingKey 需要与交换机类型(Exchange),绑定建(BindingKey)联合使用才能最终生效。
    2. 在交换机类型(Exchange),绑定建(BindingKey)固定的情况下,生产者发送消息给交换机时,通过指定路由键(RoutingKey)来决定消息流向哪里。

1.2.7、Binding(绑定)

  1. 将交换机(Exchange)与队列 Queue关联起来。
  2. 在绑定的时候,一般会指定一个绑定键 BindingKey,这样,RabbitMQ 就知道如何正确地将消息路由到队列了。
  3. 生产者将消息发送给交换机(Exchange)时,需要一个RoutingKey(路由键),当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。
  4. 在绑定多个队列到同一个交换机的时候,这些绑定允许使用相同的 BindingKey。
  5. BindingKey并不是在所有的情况下都生效,它依赖于交换机类型。
    1. 如:fanout类型的交换机就会无视BindingKey,而是将消息路由到所有绑定到该交换机的队列中。

在这里插入图片描述

1.2.8、Queue(队列)

  1. 保存消息,并转发给消费者
  2. 消息一直在队列里面,等待消费者连接到这个队列并将它取走

1.2.9、Message(消息)

  1. 应用的消息数据
  2. 由消息头和消息体组成
  3. 消息头(Properties):由一系列的可选属性组成,这些属性包括如下参数
    1. routing-key (路由键)
    2. priority (优先级)
    3. delivery-mode (消息可能需要持久性存储[消息的路由模式])
  4. 消息体(body:是不透明的

1.2.10、Producer(Publisher,消息生产者)

  1. 消息生产者,也是一个向交换器发布消息的客户端应用程序
  2. 将消息发送到RabbitMQ的服务节点上

1.2.11、Consumer(消息消费者)

  1. 消息消费者
  2. 表示从一个消息队列中取得消息的客户端应用程序

2、架构相关

2.1、消息存储

2.1.1、RabbitMQ对于queue中的message的保存方式

  1. disc(硬盘)
  2. ram(内存)

2.1.2、disc(硬盘)

采用disc,则需要对exchange,queue,delivery mode 设置成durable模式。

好处:当RabbitMQ失效了,message仍然可以在重启之后恢复。

2.1.3、ram(内存)

  1. 处理message的效率比:
    1. ram:disc=3:1
  2. 如果有其它HA手段保障的情况下,选用ram方式是可以提高消息队列的工作效率的。
  3. 使用ram方式,RabbitMQ能够承载的访问量则取决于可用的内存大小。

2.1.4、消息文件

  1. 所有队列中的消息都以append的方式写到一个文件中,当这个文件的大小超过指定的限制大小后,关闭这个文件再创建一个新的文件供消息的写入。
  2. 文件名(*.rdq)从0开始然后依次累加。
  3. 在进行消息的存储时,rabbitmq会在ets表中记录消息在文件中的映射,以及文件的相关信息。消息读取时,根据消息ID找到该消息所存储的文件,在文件中的偏移量,然后打开文件进行读取。

2.1.5、rabbitmq在启动时会创建以下两个进程

  1. msg_store_persistent进程:用于持久消息的存储
  2. msg_store_transient进程:用于内存不够时,将存储在内存中的非持久化数据转存到磁盘中
  3. 队列消息的写入和删除
    1. 由这两个进程负责处理
  4. 消息的读取
    1. 可能是队列本身直接打开文件进行读取
    2. 可能是发送请求由msg_store_persisteng/msg_store_transient进程进行处理。

2.1.6、Ets数据结构

-record(msg_location,{ msg_id,     //消息ID

                        ref_count,  //引用计数

                        file,       //消息存储的文件名

                        offset,     //消息在文件中的偏移量

                        total_size  //消息的大小

                      }).

2.1.7、日志文件数据结构

-record(file_summary,{ file,       //文件名

                        valid_total_size, //文件有效数据大小

                        left,       //位于该文件左边的文件

                        right,      //位于该文件右边的文件

                        file_size,  //文件总的大小

                        locked,     //上锁标记 垃圾回收时防止对文件进行操作

                        readers     //当前读文件的队列数

})

2.2、GC过程

2.2.1、消息的删除

  1. 从flying_ets表删除指定消息的相关信息
  2. 更新 消息对应存储的文件 的相关信息
  3. 更新 文件有效数据 大小。

2.2.2、垃圾回收时机

  1. 当垃圾数据超过一定比例后,rabbitmq触发垃圾回收。
  2. 默认比例:40%

2.2.3、垃圾回收流程

在这里插入图片描述

上面X表示无效数据。

步骤1、找到符合要求的两个文件

根据 #file_summary{} 中left,right找逻辑上相邻的两个文件,并且两个文件的有效数据可在一个文件中存储

步骤2、整理两个文件

  1. 先锁定这两个文件
  2. 再对 左边文件 的 有效数据 进行整理
  3. 再将 右边文件 的 有效数据 写入到左边文件

步骤3、更新消息

  1. 更新消息的相关信息
    1. 消息存储的文件名
    2. 消息在文件中的偏移量
  2. 更新文件的相关信息
    1. 文件的有效数据
    2. 左边文件
    3. 右边文件

步骤4、删除文件

将右边的文件删除。

2.3、性能考虑

2.3.1、操作引用计数(flying_ets)

  1. 在进行消息的写入和删除操作前,会在flying_ets表里通过+1、-1的方式进行计数
  2. 然后投递请求给msg_store_persistent、msg_store_transient进程进行处理,进程在真正写操作或者删除之前会再次判断flying_ets中对应消息的计数,决定是否需要进行相应操作。
  3. 好处:
    1. 对于频繁写入和删除的操作,减少实际的写入和删除次数。

2.3.2、尽可能的并发读

  1. 在读取消息的时候,都先根据消息ID找到对应存储的文件。
    1. 如果存储文件存在 且 没被锁住
      1. 则直接打开文件,从指定位置读取消息的内容。
    2. 如果存储文件不存在 或者 被锁住
      1. 则发送请求,由msg_store_persistent/msg_store_transient进程进行处理。

2.3.3、利用flying_ets表进行缓存

  1. 对于当前正在写的文件,所有消息在写入前都会在cur_file_cache_ets表中存一份
  2. 消息读取时会优先从cur_file_cache_ets表查找。
  3. 文件关闭时,会将cur_file_cache_ets表中引用计数为0的消息进行清除。

2.3.4、利用file_handle_cache的写缓存

  1. rabbitmq中对文件的操作转到了file_handle_cache模块(缓存模块),写模式打开文件时
    1. 进行文件的写操作,是先写入到这个缓存中,当缓存超过缓存空间大小或者显式刷新,才将缓存中的内容刷入磁盘中。
  2. file_handle_cache默认有1M的缓存空间

2.4、流控

2.4.1、阈值

  1. RabbitMQ可以对 内存和磁盘使用量 设置阈值,当达到阈值后,生产者将被阻塞(block),直到对应项恢复正常。
  2. 阈值有2个

2.4.2、没有流控 引发的 问题

Erlang进程之间并不共享内存(binaries类型除外),而是通过消息传递来通信,每个进程都有自己的进程邮箱。

Erlang默认没有对进程邮箱大小设限制,所以当有大量消息持续发往某个进程时,会导致该进程邮箱过大,最终内存溢出并崩溃。

在RabbitMQ中,如果生产者持续高速发送,而消费者消费速度较低时,如果没有流控,很快就会使内部进程邮箱大小达到内存阈值,阻塞生产者(得益于block机制,并不会崩溃)。然后RabbitMQ会进行page操作,将内存中的数据持久化到磁盘中。

为了解决该问题,RabbitMQ使用了一种基于信用证的流控机制。用流控(Flow Control)机制来确保稳定性。

2.4.3、基于信用证的流控机制

  1. 消息处理进程有一个信用组{InitialCredit,MoreCreditAfter},默认值为{200, 50}。
  2. 消息发送者(进程A) 向 接收者(进程B)发消息,每发一条消息,Credit数量减1,直到为0,A被block住
  3. 对于接收者(进程B),每接收MoreCreditAfter条消息,会向A发送一条消息,给予A MoreCreditAfter个Credit,当A的Credit>0时,A可以继续向B发送消息。

2.4.3.1、RabbitMQ生产消息传输路径

在这里插入图片描述

  1. 从上可以看出,基于信用证的流控 最终将 消息发送进程 的 发送速度 限制在 消息处理进程 的 处理速度内。R
  2. abbitMQ中与流控有关的进程构成了一个有向无环图。

3、MQ队列详解–普通队列

3.1、普通队列的结构

在这里插入图片描述

3.2、队列由两部分组成

  1. AMQQueue:负责AMQP协议相关的消息处理,具体如下
    1. 接收生产者发布的消息
    2. 向消费者投递消息
    3. 处理消息confirm、acknowledge等等
  2. BackingQueue:
    1. 提供了相关的接口供AMQQueue调用
    2. 完成消息的存储以及可能的持久化工作等。

3.3、多层队列结构的设计

  1. 上图的BackingQueue由5个子队列组成:Q1, Q2,Delta,Q3,Q4。
  2. RabbitMQ中的消息一旦进入队列,不是固定不变的,它会随着系统的负载在队列中不断流动,消息不断发生变化。
  3. 在BackingQueue中,消息的生命周期分为4个状态
    1. Alpha:消息的内容和消息索引都在RAM中。Q1和Q4的状态。
    2. Beta:消息的内容保存在DISK上,消息索引保存在RAM中。Q2和Q3的状态。
    3. Gamma:消息内容保存在DISK上,消息索引在DISK和RAM都有。Q2和Q3的状态。
    4. Delta:消息内容和索引都在DISK上。Delta的状态。

上述就是RabbitMQ的多层队列结构的设计,我们可以看出从Q1到Q4,基本经历RAM->DISK->RAM这样的过程。

3.4、多层队列结构设计的好处

  1. 当队列负载很高的情况下,能够通过将一部分消息由磁盘保存来节省内存空间
  2. 当负载降低的时候,这部分消息又渐渐回到内存,被消费者获取,使得整个队列具有很好的弹性。

3.5、消息队列的工作流程

3.5.1、引起消息流动的主要2个因素

  1. 消费者获取消息
  2. 由于内存不足,引起消息从内存 转移到 磁盘。

3.5.2、消息流动

  1. RabbitMQ 根据 消息传输的速度,计算一个当前内存中能够保存的 最大消息数量(Target_RAM_Count)
  2. 当内存中的消息数量 > Target_RAM_Count,就会引起消息流动。
  3. 进入队列的消息,一般会按照Q1->Q2->Delta->Q3->Q4的顺序进行流动,但是并不是每条消息都一定会经历所有的状态,这个取决于当前系统的负载状况。

3.5.3、消费者获取消息 引起的消息流动过程

在这里插入图片描述

步骤1、从Q4中获取消息

  1. 当消费者获取消息时,首先会从Q4队列中获取消息
    1. 如果Q4非空,则返回,到这里流程就结束。
    2. 如果Q4为空,从Q3获取消息,执行步骤2

步骤2、从Q3中获取消息

  1. 如果Q3为空,即此时队列中无消息(后续会论证)。
  2. 如果Q3非空,则取出Q3的消息
    1. 判断此时Q3和Delta队列的长度
      1. 如果都为空
        1. 则可认为Q2、Delta、Q3、Q4全部为空(后续会论证),此时将Q1中消息直接转移到Q4中,下次直接从Q4中获取消息
      2. 如果Q3为空,Delta不为空
        1. 则将Delta转移到Q3中
      3. 如果Q3不为空
        1. 则直接下次从Q3中获取消息
  3. Delta转移到Q3的过程
    1. RabbitMQ是按照索引分段读取的,首先读取某一段,直到读到的消息非空为止,然后判断读取的消息个数与Delta中的消息个数是否相等
      1. 如果相等,则断定此时Delta中已无消息,则直接将Q2和刚读到的消息一并放入Q3中
      2. 如果不相等,则仅将此次读取到的消息转移到Q3。

3.5.4、消息换出的条件

  1. 消息换出的条件是内存中保存的消息数量+等待ACK的消息的数量>Target_RAM_Count。
  2. 当条件触发时,系统首先会判断如果当前进入等待ACK的消息的速度大于进入队列的消息的速度时,会先处理等待ACK的消息。

3.5.5、为什么Q3队列为空即可以认定整个队列为空?

  1. 试想如果Q3为空,Delta不空,则在Q3取出最后一条消息时,则会将Delta的消息转移到Q3,与Q3空矛盾。
    1. 结论:Q3为空,Delta一定为空
  2. 如果Q2不空,则在Q3取出最后一条消息时,因为Delta为空,则会将Q2的消息转移到Q3,与Q3为空矛盾
    1. 结论:Q3为空,Q2一定为空
  3. 如果Q1为空,则在Q3取出最后一条消息时,因为Delta和Q2均为空,则将Q1的消息转移到Q4中,与Q4为空矛盾。
    1. Q3为空,Q1一定为空

总结

  1. Q3和Delta为空,Q2就为空
  2. Q3为空,整个队列为空

3.5.6、消息的生命周期变化的场景

3.5.6.1、负载正常情况

消息被消费的速度 大于等于 接收新消息的速度,对于不需要保证可靠不丢的消息极可能只会有Alpha状态。

对于durable=true的消息,它一定会进入gamma状态,若开启publish confirm机制,只有到了这个阶段才会确认该消息已经被接受,若消息消费速度足够快,内存也充足,这些消息也不会继续走到下一状态。

3.5.6.2、负载较高情况

消息被消费的速度 小于 接收新消息的速度,这些消息就会进入到很深的队列中去,增加处理每个消息的平均开销。

因为要花更多的时间和资源处理"积压"的消息,所以用于处理新来的消息的能力就会降低,使得后来的消息又被积压进入很深的队列,继续加大处理每个消息的平均开销,这样情况就会越来越恶化,使得系统的处理能力大大降低。

根据官网资料,应对这一问题,有三个措施:

1. 进行流量控制。
2. 增加prefetch的值,即一次发送多个消息给接收者,加快消息被消费掉的速度。
3. 采用multipleack,降低处理ack带来的开销。

4、MQ队列详解–镜像队列

4.1、镜像队列的结构

在这里插入图片描述

  1. 镜像队列就是一个特殊的BackingQueue
    1. 内部包裹了一个普通的BackingQueue做本地消息持久化处理,在此基础上增加了将消息、ack复制到所有镜像的功能。
  2. 所有对mirror_queue_master的操作,会通过组播GM的方式同步到各slave节点。

4.1.1、GM

负责消息的广播

4.1.2、mirror_queue_slave

  1. 负责回调处理
  2. mirror_queue_slave中包含了普通的BackingQueue,进行消息的存储

4.1.3、coordinator

负责master上的回调处理

4.1.4、mirror_queue_master

  1. mirror_queue_master中包含了BackingQueue,进行消息的存储
  2. BackingQueue由AMQQueue进行调用。

4.1.4、master节点

  1. 完成 消息的发布(除了Basic.Publish之外)
  2. 完成 消息的消费
  3. master节点对消息进行处理的同时将消息的处理动作通过GM广播给所有的slave节点,slave节点的GM收到消息后,通过回调交由mirror_queue_slave进行实际的处理。

4.1.4、master节点

对于Basic.Publish,消息同时发送到master和所有slave上,如果此时master宕掉了,消息还发送slave上,这样当slave提升为master的时候消息也不会丢失。

4.2、组播(GM,Guarenteed Multicast)

GM模块实现的一种可靠的组播通讯协议,该协议能够保证组播消息的原子性,即保证组中活着的节点要么都收到消息要么都收不到。

4.2.1、组播通讯协议的实现

  1. 将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点
    1. 当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上;
    2. 当有节点失效时,相邻的节点会接管保证本次广播的消息会复制到所有的节点。
  2. 在master节点和slave节点上的这些gm形成一个group,group(gm_group)的信息会记录在mnesia中。
  3. 不同的镜像队列形成不同的group。
  4. 从master节点发出gm后,消息顺着链表依次传送到所有的节点,由于所有节点组成一个循环链表,master节点对应的gm最终会收到自己发送的消息,这个时候master节点就知道消息已经复制到所有的slave节点了。

在这里插入图片描述

4.3、节点的失效

  1. 如果某个slave失效了,系统除了做些记录外几乎啥都不做
    1. master依旧是master,客户端不需要采取任何行动,或者被通知slave失效。
  2. 如果master失效了,那么slave中的一个必须被选中为master。
    1. 被选中作为新master的slave通常是最老的那个,因为最老的slave与前任master之间的同步状态应该是最好的。
    2. 注意:如果存在没有任何一个slave与master完全同步的情况,那么前任master中未被同步的消息将会丢失。

4.4、消息的同步

4.4.1、ha-sync-mode=manual(默认)

  1. 当新节点加入已存在的镜像队列,镜像队列中的消息不会主动同步到新节点
  2. 只有显式调用同步命令,镜像队列中的消息才回主动同步到新节点
    1. 当调用同步命令后,队列开始阻塞,无法对其进行操作,直到同步完毕。

4.4.1、ha-sync-mode=automatic(默认)

  1. 当新节点加入已存在的镜像队列,镜像队列中的消息会自动同步到新节点
  2. 由于同步过程的限制,所以不建议在生产的active队列(有生产消费消息)中操作。

4.4.3、命令

# 查看那些slaves已经完成同步
rabbitmqctl list_queues 

# 手动的方式同步一个queue
rabbitmqctl sync_queue name

# 取消某个queue的同步功能
rabbitmqctl cancel_sync_queue name

5、 集群原理

5.1、基本概念

5.1.1、镜像队列(Mirrored Queue)

  1. 默认情况,集群的队列只存在单一节点上
  2. 可以把队列配置为同时存在在多个节点上,也就是说队列可以被镜像到多个节点上。
    1. 发布(publish)到镜像队列上的消息(message)会被复制(replicated)到所有的节点上。
  3. 一个镜像队列包含一个主(master)和多个从(slave)。

5.1.2、非同步的Slave(unsynchronised slave)

在rabbitmq中同步(synchronised)是用来描述master和slave之间的数据状态是否一致的。

  1. 如果slave包含master中的所有message,则这个slave是synchronised。
  2. 如果slave没有包含master中所有的message,则这个slave是unsynchronised。

5.1.3、在什么情况下会出现 unsynchronised slave?

5.1.3.1、场景1

当一个新slave加入到一个镜像队列时,这时这个新slave是空的,而master中这时可能包含之前接收到的消息。假设这时master包含了N条消息,当第N+1条消息被添加到这个镜像队列中,这个新slave会从这个第N+1条消息开始接收。

  1. 此时这个slave就是unsynchronised slave。
  2. 随着前N条消息从镜像队列中被消费掉(consumed), 这个slave变成了synchronised。

5.1.3.2、场景2

  1. slave 重新加入(rejoin)到镜像队列时,也会出现非同步的情况。
  2. 一个slave要重新加入镜像队列之前,slave可能已经接收了一些消息,要重新加入镜像队列,就要清空自己之前已经接收的所有消息,好像自己是第一次加入队列一样。
  3. slave需要重新加入镜像队列原因,举例如下
    1. 网络分区(networkpartition)

5.2、服务可用性(Availablity)与数据可靠性(Reliability)

5.2.1、选主方式

由RabbitMQ的slave加入和重新加入队列的方式,我们得出一个结论,越早加入队列的slave,越有更大的机会是同步状态的,所以RabbitMQ通过下面方式选主

  1. 当master因为某种原因失效时,最早加入镜像队列的slave被提升成master。

5.2.2、RabbitMQ的可用性(Availablity)和数据可靠性(Reliability)

RabbitMQ通过参数配置的方式,在可用性和数据可靠性做出了一定的权衡。

5.2.2.1、参数:ha-sync-mode

  1. 这个参数是可用性和可靠性的一个平衡
  2. 值:
    1. munual(默认值)
    2. automatic
5.2.2.1.1、ha-sync-mode=munual
  1. 是默认值
  2. 不保证数据可靠性,在某些情况会出现丢消息的可能,但是保证了队列的可用性。
  3. 当一个slave加入和重新加入队列时的时候,我们可以通过命令手工(munual)同步。
# 手工(munual)同步命令
rabbitmqctl sync_queue name
5.2.2.1.2、ha-sync-mode=automatic

当一个新slave加入时,slave会自动同步master中的所有消息,在所有消息被同步完成之前,所有的操作都会被阻塞(blocking),会出现队列的暂时不可用。

5.2.2.2、参数:ha-promote-on-shutdown

  1. 用来控制选主的行为
  2. 这个参数是可用性和可靠性的一个平衡
    1. when-synced
    2. always

ha-promote-on-shutdown=when-synced

  1. 是默认值
  2. 在可控的master关闭时(比如停止RabbitMQ服务或者关闭操作系统),RabbitMQ会拒绝把一个非同步的slave提升成新的master。
    1. 只有在非可控的master关闭时(比如server crash, 断网),才会故障恢复到一个非同步的slave。
  3. 可靠性更好,可用性降低了,因为如果所有的slave都是非同步状态,那就没有符合条件的slave可以被提升成master,这时队列就处在不可用状态。

ha-promote-on-shutdown=always

在所有情况下,master关闭时,RabbitMQ可以把一个非同步的slave提升成新的master。

5.3、集群原理

5.3.1、单节点

  1. 节点的失效将导致整个服务临时性的不可用
  2. 可能会导致message的丢失,以下场景就会发生
    1. 在非持久化message存储于非持久化queue中的时候

5.3.1.1、防止消息丢失设置

  1. 将所有的publish的message都设置为持久化
  2. 将queue设置为持久化
  3. 以上的配置,仍然会丢失消息,只是概率很低

5.3.1.2、为什么 以上的配置,仍然会丢失消息

  1. 如果 生产者 发送消息之后,且被同步到磁盘之前,MQ服务挂了,这个时候消息就会丢失了。
  2. 可以通过publisher的confirm机制能够确保客户端知道哪些message已经存入磁盘

5.3.2、普通队列

  1. 如果RabbitMQ集群是由多个broker节点构成的,那么从服务的整体可用性上来讲,该集群对于单点失效是有弹性的
  2. 注意:
    1. 尽管exchange和binding能够在单点失效问题上幸免于难,但是queue和其上持有的message却不行,这是因为queue及其内容仅仅存储于单个节点之上,所以一个节点的失效表现为其对应的queue不可用。

5.3.3、镜像队列

RabbitMQ的镜像队列机制是将queue镜像到cluster中其他的节点之上。

在通常的用法中,针对每一个镜像队列都包含一个master和多个slave,分别位于于不同的节点。slave会准确地按照master执行命令的顺序进行命令执行,故slave与master上维护的状态应该是相同的。

所有动作都只会向master发送,然后由master将命令执行的结果广播给slave们,故看似从镜像队列中的 消费操作 实际上是在master上执行的。

在该实现下,如果镜像队列中的一个master失效了,集群自动选出一个slave(最老的slave)提升为master,此后message可以继续发送到队列上。

5.3.3.1、镜像队列 支持 publisher confirm和事务 两种机制

  1. 在事务机制中,只有当前事务在全部镜像queue中执行之后,客户端才会收到Tx.Commit Ok的消息。
  2. 在publisher confirm机制中,只有message被全部镜像所接受了,才 向publisher 进行 当前message的确认。

6、集群补充要点

6.1、镜像队列不能作为负载均衡使用

因为每个操作在所有节点都要做一遍。

6.2、ha-mode参数和durable declare参数 对exclusive队列都不生效

因为exclusive队列是连接独占的,当连接断开,队列自动删除。所以实际上这两个参数对exclusive队列没有意义。

6.3、当所有slave都处在(与master)未同步状态时,并且ha-promote-on-shutdown=when-synced

6.3.1、如果master因为主动的原因停掉

比如是通过rabbitmqctl stop命令停止或者优雅关闭OS,那么slave不会接管master,也就是说此时镜像队列不可用

6.3.2、如果master因为被动原因停掉

  1. 比如 VM 或者OS crash了,那么slave会接管master。
  2. 这个配置项隐含的价值取向是优先保证消息可靠不丢失,放弃可用性。

6.4、当所有slave都处在(与master)未同步状态时,并且ha-promote-on-shutdown=when-alway

不论master因为何种原因停止,slave都会接管master,优先保证可用性。

6.5、镜像队列节点 启动和停止 顺序

  1. 镜像队列中最后一个停止的节点会是master
  2. 启动顺序必须是master先启动
    1. 如果slave先启动,它会有30s的等待时间,等待master的启动,然后加入cluster中 1. 如果30s内master没有启动,slave会自动停止
  3. 当所有节点因故(断电等)同时离线时,每个节点都认为自己不是最后一个停止的节点。
    1. 要恢复镜像队列,可以尝试在30s之内启动所有节点。

6.6、客户端Basic.Publish操作

客户端Basic.Publish操作,消息会同步到所有节点(master和所有slave),如果此时master宕掉了,消息还发送slave上,这样当slave提升为master的时候消息也不会丢失

6.7、客户端其他操作

  1. 通过master中转,再由master将操作作用于slave。
  2. 举例:
    1. 一个Basic.Get操作,假如客户端与slave建立了TCP连接,首先是slave将Basic.Get请求发送至master,由master备好数据,返回至slave,投递给消费者。

6.8、slave宕掉的影响

除了与slave相连的客户端连接全部断开之外,没有其他影响。

6.9、master宕掉的影响

6.9.1、与master相连的客户端连接全部断开

6.9.2、选举最老的slave节点为master

如果此时所有slave处于未同步状态,则未同步部分消息丢失

6.9.3、新的master节点 重新执行 所有unack消息

  1. 因为 新的master节点 无法区分 这些unack消息的以下情况
    1. unack消息是否已经到达客户端
    2. ack消息是否丢失在老的master的链路
    3. ack消息是否丢失在master组播ack消息到所有slave的链路
  2. 所以处于消息可靠性的考虑,重新执行 所有unack的消息。此时客户端可能有重复消息

6.9.4、x-cancal-on-ha-failover参数 情况

  1. 如果客户端连着slave,并且Basic.Consume消费时指定了x-cancel-on-ha-failover参数,那么客户端会受到一个ConsumerCancellation Notification通知,Java SDK中会回调Consumer接口的handleCancel方法,故需覆盖此方法。
  2. 如果客户端连着slave,如果未指定x-cancal-on-ha-failover参数,那么消费者就无法感知master宕机,会一直等待下去。

7、镜像队列的恢复

7.1、前提

  1. 两个节点A和B组成 镜像队列
  2. A是slave
  3. B是master

7.2、场景1:A先停,B后停

  1. 方法1:只要先启动B,再启动A即可。
  2. 方法2:先启动A,再在30s之内启动B即可恢复镜像队列。
    1. 如果没有在30s内恢复B,那么A自己就停掉自己

7.3、场景2:A,B同时停

该场景下可能是由掉电等原因造成,只需在30s内连续启动A和B即可恢复镜像队列。

7.4、场景3:A先停,B后停,且A无法恢复。

  1. 先等B起来后,在B节点上调用rabbitmqctl forget_cluster_node A 以解除A的cluster关系
  2. 再将新的slave节点 加入B
  3. 以上操作:即可重新恢复镜像队列。

7.5、场景4:A先停,B后停,且B无法恢复

7.5.1、forget_cluster_node支持-offline参数

offline参数允许rabbitmqctl在离线节点上执行forget_cluster_node命令,迫使RabbitMQ在未启动的slave节点中选择一个作为master。

7.5.2、操作

  1. 当在A节点执行 rabbitmqctl forget_cluster_node-offline B时,RabbitMQ会 模拟一个节点代表A,执行forget_cluster_node命令将B提出cluster,然后A就能正常启动了。
  2. 最后将新的slave节点加入A即可重新恢复镜像队列

7.1.5、场景5:A先停,B后停,且A和B均无法恢复,但是能得到A或B的磁盘文件

  1. 先将A或B的数据库文件($RabbitMQ_HOME/var/lib目录中)copy至新节点C的目录下
  2. 再将C的hostname改成A或者B的hostname。
    1. 如果拷贝过来的是A节点的磁盘文件,按场景4处理
    2. 如果拷贝过来的是B节点的磁盘文件,按场景3处理。
  3. 最后将新的slave节点加入C即可重新恢复镜像队列。

7.1.6、场景6:A先停,B后停,且A和B均无法恢复,且无法得到A和B的磁盘文件

无法处理

8、参考博客

官网文档 
http://www.rabbitmq.com

RabbitMQ 队列镜像配置主机挂掉之后自动切换 另一台 
http://blog.csdn.net/csethcrm/article/details/53928313

  
RabbitMQ不同Confirm模式下的性能对比 
http://ju.outofmemory.cn/entry/177937

  

相关文章:

  • 网页设计与开发-实验报告-1
  • 电阻应变式力传感器
  • 深度学习数据集最常见的6大问题
  • 打开算法之门,算法学习瓶颈、学习方法
  • Mybatis的事务管理机制。
  • 微信小程序通过字典表匹配对应数据
  • 嵌入式系统开发笔记102:DEV C++的使用
  • 37.(前端)菜单的展示
  • 计算机的硬件(计算机组成原理)
  • Linux权限理解
  • 注册商标的重要性
  • 65.【Study_kuang 多线程】
  • 【JavaScript】事件高级
  • STM32使用PWM+DMA方式驱动WS2812灯珠
  • 【vue】html页面怎么设置页签图标?比如B站是个小电视的图标的效果
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 0x05 Python数据分析,Anaconda八斩刀
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • idea + plantuml 画流程图
  • Linux CTF 逆向入门
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • 力扣(LeetCode)21
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 收藏好这篇,别再只说“数据劫持”了
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 一天一个设计模式之JS实现——适配器模式
  • 【云吞铺子】性能抖动剖析(二)
  • ionic入门之数据绑定显示-1
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • Spring第一个helloWorld
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • #include<初见C语言之指针(5)>
  • #vue3 实现前端下载excel文件模板功能
  • #大学#套接字
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
  • (10)ATF MMU转换表
  • (39)STM32——FLASH闪存
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (翻译)terry crowley: 写给程序员
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (十一)图像的罗伯特梯度锐化
  • (四)Android布局类型(线性布局LinearLayout)
  • (一)Kafka 安全之使用 SASL 进行身份验证 —— JAAS 配置、SASL 配置
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .net Signalr 使用笔记
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)