MQ - RabbitMQ镜像队列机制

2023年10月2日
大约 10 分钟

MQ - RabbitMQ镜像队列机制

镜像队列在解决什么?

  • 镜像队列 (Mirror Queue) 机制,可以将队列镜像到集群中的其他Broker节点之上。当集群中的一个节点失效了,队列能自动地切换到镜像中的另一个节点上,以此可以保证服务的可用性,而不是节点实现其上的队列不可用。

1. 机制原理

一般情况下,给每一个配置镜像的队列,即镜像队列都包含一个主节点(master) 和若千个从节点 (slave)。如下图,给qingjun_queue队列配置镜像包含了一个主节点和两个从节点。这里要注意镜像的主从节点和rabbitmq服务主从节点的区别。

实现原理:

  • 镜像队列的slave节点会准确地按照镜像队列的master节点执行命令的顺序进行动作,所以镜像队列的master节点和slave节点数据是相同的,维护状态也应该是相同的。
  • 当镜像队列的master节点失效,"资历最老"的镜像队列slave1就会被提升为新的master。选主机制是根据镜像队列save节点加入时间顺序来的,时间最长的slave即为“资历最老”。
  • 发送到镜像队列的所有消息会被同时发往master和所有的slave上,如果此时master挂掉了,消息还会在slave上,这样 slave提升为 master 的时候消息也不会丢失。除发送消息 (Basic.Publish) 外的所有动作都只会向master发送,然后再由master将命令执行的结果广播给各个slave。

注意事项:

  • 若消费者与slave建立连接并进行订阅消费,其实质上都是从master上获取消息,只不过看似是从 slave 上消费而已。
  • 比如消费者与slave建立了TCP连接之后执行一个Basic.Get的操作,那么首先是由slave将Basic.Get请求发往master,再由master准备好数据返回给slave,最后由slave投递给消费者。

图

1.1. 集群结构

如下图,集群一共三个节点,broker1、broker2、broker3。每个broker节点都包含1个镜像队列的master和2个镜像队列的slave。

  • qingjun_queue镜像队列的负载大多都集中在broker1上,baimu_queue镜像队列的负载大多都集中在broker2上,wuhan_queue镜像队列的负载大多都集中在broker3上。
  • 只要确保镜像队列的master节点均匀散落在集群中的各Broker节点上就可以确保很大程度上的负载均衡。
    • 为什么不是绝对? 因为每个队列的流量会有不同,因此均匀散落各个队列的master也无法确保绝对的负载均衡)。

注意事项:

rabbitmq镜像队列同时支持发送方确认机制(publisher confirm)和事务机制。

  • 在事务机制中,只有当前事务在全部镜像中执行之后,客户端才会收到Tx.Commit-ok的消息。
  • publisher confirmm机制中,生产者进行当前消息确认的前提是该消息被全部进行所接收了。

图

2. 镜像结构

镜像队列结构和普通队列结构不同点在于backing_queue的不同,普通队列使用的是rabbit_variable_queue,而镜像队列使用的backing_queue内部包裹了普通backing_queue进行本地消息消息持久化处理,在此基础上增加了将消息和ack复制到所有镜像的功能。

如下图,master使用的backing_queue是rabbit_mirror_queue_master,slave使用的backing_queue是rabbit_mirror_queue_slave

普通队列结构

图

镜像队列结构

图

2.1. 组播GM

GM的作用:

  • 所有对rabbit_mirror_queue_master的操作都会通过组播 GM(GuaranteedMulticast)的方式同步到各个slave中。
  • GM 负责消息的广播,rabbit_mirror_queue_slave负责回调处理,而master上的回调处理是由coordinator负责完成的。
  • 除了Basic.Publish,所有的操作都是通过 master 来完成的,master 对消息进行处理的同时将消息的处理通过GM广播给所有的slave,slave的GM收到消息后,通过回调交由rabbit_mirror_queue_slave进行实际的处理。

2.1.1. 实现原理

GM 模块是通过组播通信协议来实现,该协议能够保证组播消息的原子性。换言之,保证组中活着的节点要么都收到消息,要么都收不到。

实现过程:

将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点。

  • 当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上。
  • 当有节点失效时,相邻的节点会接管以保证本次广播的消息会复制到所有的节点。

master和slave上的组播GM会形成一个组 (gm group),这个组的信息会记录在Mnesia中。

不同的镜像队列形成不同的组。操作命令从master对应的GM发出后,顺着链表传送到所有的节点。由于所有节点组成了一个循环链表,master 对应的 GM 最终会收到自己发送的操作命令,这个时候 master 就知道该操作命令都同步到了所有的 slave 上。

图

2.1.2. 加入新节点

如下图,新增了一个节点4,整个过程就像在链表中间插入一个节点。

注意事项:

每当个节点加入或者重新加入到这个镜像链路中时,之前队列保存的内容会被全部清空。

图

2.1.3. 节点宕机的影响

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

当master挂掉之后,会有以下连锁反应:

  1. 先与master连接的客户端连接全部断开。
  2. 选举最老的slave作为新的master。因为最老的slave与旧的master之间的同步状态是最好的。如果此时所有slave处于未同步状态,则未同步的消息会丢失。
  3. 新的master重新入队所有unack的消息。因为新的slave无法区分这些unack的消息是否已经到达客户端,或者是ack信息丢失在老的master链路上,再或者是丢失在老的master组播ack消息到所有slave的链路上,所以出于消息可靠性的考虑,重新入队所有unack的消息,带来的弊端就是客户端可能会有重复消息。
  4. 若客户端连接着slave,并且Basic.Consume消费时指定了x-cancel-on-ha:failover参数,那么断开时客户端会收到一个Consumer Cancellation Notifcation的通知,消费者客户端中会回调consumer接口的 handleCancel 方法。如果未指定x-cancel-on-ha-failover参数,那么消费者将无法感知master宕机。

3. 配置镜像队列

镜像队列的配置主要是通过添加相应的Policy来完成的,两种方式,一是在代码里提前写好配置,二是使用管理命令配置。

注意事项:

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

3.1 定义参数

参数含义:

  • ha-mode:指明镜像队列的模式,可选项有 all、exactly、nodes,默认为 all。
    • all:表示在集群中所有的节点上进行镜像。
    • exactly:表示在指定个数的节点上进行镜像,节点个数由 ha-params 指定。
    • nodes: 表示在指定节点上进行镜像,节点名称由 ha-params 指定,节点的名称通常类似于 rabbit@hostname ,可以通过rabbitmqctl_cluster_status命令查看。
  • ha-params:不同的 ha-mode 配置中需要用到的参数。
  • ha-sync-mode:队列中消息的同步方式,可选项有 automatic(自动) 和manual(手动)。
  • ha-promote-on-shutdown:可选参数when-synced(何时同步)和always(始终同步)。
  • ha-promote-on-failure:可选参数when-synced(何时同步)和always(始终同步)。

注意事项:

  • ha-mode参数对排他(exclusive) 队列不生效,因为排他队列是连接独占的,当连接断开时队列会自动删除,所以实际上这个参数对排他队列没有任何意义。
  • 将新节点加入已存在的镜像队列时,默认情况下ha-sync-mode取值为manual(手动),镜像队列中的消息不会主动同步到新的slave中,除非显式调用同步命令。当调用同步命令后,队列开始阻塞,无法对其进行其他操作,直到同步完成。
  • 当 ha-sync-mode 设置为 automatic(自动)时,新加入的slave会默认同步已知的镜像队列。由于同步过程的限制,所以不建议对生产环境中正在使用的队列进行操作。
  • 当所有slave都出现未同步状态,且ha-promote-on-shutdown参数设为always,那么不论 master 因为何种原因停止,slave 都会接管 master,优先保证可用性,存在消息丢失情况。当ha-promote-on-shutdown参数设置为 when-synced(默认)时,分两种情况:
    • 若master因主动原因停掉,比如通过rabbitmgctl stop命令停止,那么slave不会接管 master,此时镜像队列不可用。
    • 若master因被动原因停掉,比如 Erlang 虚拟机或者操作系统崩溃,那么 slave 会接管 master。

图

3.2 命令配置

命令格式:

rabbitmqctl set_policy [-p vhost] [–prioritypriority] [–apply-to apply-to] {name} {pattern} {definition}

参数释义:

  • [-p vhost]参数:可选参数,针对指定vhost下的queue进行设置。
  • [–prioritypriority]:可选参数,policy的优先级。
  • [–apply-to apply-to ]参数:可选参数,指定对象为交换器还是队列。
  • {name}参数:自定义策略名称。
  • {pattern}参数: queue的匹配模式(正则表达式)
  • {definition}参数:ha-mode 、ha-params、ha-sync-mode三部分。

1. 查看要进行镜像的队列。

图

2. 设置策略。

# 优先级为0。
#指定对象为所有交换器。
#策略名称为qingjun_policy。
#匹配正则为"以qingjun开头的所有交换器"。
#镜像策略为,在3个节点上进行镜像,并设置消息同步方式为自动同步。
[root@node1 ~]# rabbitmqctl set_policy --priority 0 --apply-to exchanges  qingjun_policy  "^qingjun*" '{"ha-mode":"exactly","ha-params":3,"ha-sync-mode":"automatic"}'

图

查看结果。

图

  1. 查看状态

图图

3.4 相关命令

3.4.1 查看节点消息同步状态

[root@node1 ~]# rabbitmqctl  list_queues name slave_pids synchronised_slave_pids

图

3.4.2 取消某队列同步消息功能

取消qingjue_queue队列的消息同步功能,注意这个队列是能匹配到上面设置的镜像队列策略的。

[root@node1 ~]# rabbitmqctl  cancel_sync_queue qingjue_queue

图

3.4.3 手动同步某队列消息

手动给qingjue_queue队列进行消息同步,注意这个队列是能匹配到上面设置的镜像队列策略的。

[root@node1 ~]# rabbitmqctl  sync_queue qingjue_queue

图

引用资料

  • https://blog.csdn.net/yi_qingjun/article/details/128496818