MQ - RabbitMQ实现网络分区

2023年10月2日
大约 18 分钟

MQ - RabbitMQ实现网络分区

网络分区的意义:

  • rabbitmq集群的网络分区的容错性不是很高,一般情况可以使用Federation或者Shovel插件就可以解决广域网中的使用问题。不过即使是在局域网环境下,网络分区也不可能完全避免,网络设备(比如中继设备、网卡) 出现故障也会导致网络分区。极端情况下还会造成数据丢失,影响服务的可用性。
  • 当一个集群发生网络分区时,这个集群会分成两个部分或者更多,它们各自为政,互相都认为对方分区内的节点已经挂了,包括队列、交换器及绑定等元数据的创建和销毁都处于自身分区内,与其他分区无关。如果原集群中配置了镜像队列,而这个镜像队列又牵涉两个或者更多个网络分区中的节点时,每一个网络分区中都会出现一个master节点,对于各个网络分区,此队列都是相互独立的。当然也会有一些其他未知的、怪异的事情发生。
  • 在一致性数据模型下,若出现网络波动或者网络故障等异常情况,会导致整个数据链的性能就会大大降低。例如有A、B、C、D四节点为一个数据链,倘若其中的C节点网络异常,那么整个A——> B——> C——>D——> A的数据链就会被阻塞,继而相关服条也会被阻塞,这种情况下就需要将异常节点C剥离整个分区,以确保rabbitmq服务的可用性及可靠性。等待网络恢复之后,可以进行相应的处理来将此前的异常节点加入集群中。

网络分区的影响:

  • 数据丢失;
  • 服务阻塞不可用;
  • 网络恢复后,网络分区状态仍然存在。

1. 网络分区的判断

判断机制:

  • rabbitmq集群节点内部通信端口默认为25672,两两节点之间都会有信息交互。如果某节点出现网络故障,或者是端口不通,则会致使与此节点的交互出现中断,这里就会有个超时判定机制,继而判定网络分区。
  • 超时判定机制与net_ticktime参数有关,该参数默认值为60秒。若超时,则会有net_tick_timeout的信息报出。这个参数与heartbeat_time有些区别,heartbeat_time是指客户端与RabbitMQ服务之间通信的心跳时间,针对5672端口而言。

节点之间的应答机制(判断流程):

rabbitmq集群内部的每个节点之间会每隔四分之一的net_ticktime计一次应答 (tick)。若有任何数据被写入节点中,则此节点被认为已经被应答 (ticked) 了。如果连续 4 次,某节点都没有被 ticked,则可以判定此节点已处于“down”状态,其余节点可以将此节点剥离出当前分区。

1.1. 应答时间范围

如下图,将连续4次的tick时间记为T,那么T的取值范围为:0.75 * net_ticktime < T<1.25 * net_ticktime

图中每个节点代表一次tick判定的时间戳,在2个临界值0.75 * net_ticktime1.25 * net_ticktime之间可以连续执行4次的tick判定。默认情况下,在45s < T< 75s之间会判定出net_tick_timeout。

图

1.2. 判定方式

1.2.1. 日志方式

rabbitmq不仅会将队列、交换器及绑定等信息存储在Mnesia数据库中,而且许多围绕网络分区的一些细节也都和这个Mnesia的行为相关。

如果一个节点不能在下时间连上另一个节点,那么Mnesia通常认为这个节点已经挂了,就算之后两个节点又重新恢复了内部通信,但是这两个节点都会认为对方已经挂了,Mnesia此时认定了发生网络分区的情况。这些会被记录到RabbitMQ的服务日志之中。

图

1.2.2. 命令查看

通过命令rabbitmqctl cluster_status可以看到集群相关信息。

  • 当没有发生网络分区时,在Network Partitions这一块显示无。
  • 当发生网络分区时,Network Partitions这一块会显示相关信息。

图图

1.2.3. 监控页面提示

也可以通过 Web 管理界面来查看。如果出现了下图这种告警,即发生了网络分区。

图

1.2.4. API查看

第三种,通过HTTP API的方式调取节点信息来检测是否发生网络分区。

其中会有partitions的相关项,如果在其中发现partitions项中有内容则为发生了网络分区。举例,将node2分离出nodel和node3的主分区之后,调用/api/nodes这个接口的部分信息

在node1节点上查看,partitions一栏显示有node2节点,说明node1和node2节点发生了网络分区。

[root@node1 ~]# curl  -XGET http://192.168.130.129:15672/api/nodes?pretty -i -u qingjun:citms -H "content-type:application/json"

图

2. 模拟网络分区

模拟网络分区的方式有多种,主要分为以下3大类:

  • iptables封禁/解封IP地址或者端口号。
  • 关闭/开启网卡。
  • 挂起/恢复操作系统

2.1. 封禁端口

若是通过防火墙屏蔽端口只能模拟出net_tick_timeout,不能触发网络分区。

图

由于rabbitmq集群内部节点通信端口默认为 25672,可以封禁25672端口方式来模拟出net_tick_timeout。开启防火墙只能模拟触发节点的net_tick_timeout,但是模拟不了节点的网络分区现象。

比如我这里集群有3个节点,node1、node2、node3。此时我们要模拟 node2 节点被剥离出当前分区的情形,即模拟[nodel,node3][node2]两个分区。就在 node2节点执行封禁端口命令。

  1. 封禁node2节点上的25672端口。
[root@node2 ~]# iptables -A INPUT -p tcp --dport 25672 -j DROP
[root@node2 ~]# iptables -A OUTPUT -p tcp --dport 25672 -j DROP
  1. 此时可以查看node1和node3节点日志,显示node2已经触发了节点之间的应答机制。

图

  1. 接触node2节点上的25672封禁,此时才会判定node2节点发生了网络分区。
[root@node2 ~]# iptables -D OUTPUT 1
[root@node2 ~]# iptables -D INPUT 1
  1. 此时再去查看node1和node3节点日志,显示node2节点出现了网络分区。而node2节点上的日志显示,node1和node3节点已经掉线。

图图

  1. 分别查看三个节点上集群状态。

图图

  1. 查看web监控页面。

图

2.2. 封禁IP

假设整个rabbitmq集群的节点名称与其IP地址对应如下:

node1 192.168.130.129
node2 192.168.130.130
node3 192.168.130.131

若要模拟出[node1,node3][node2]两个分区的情形,可以在node2节点上执行封禁命令。

#封禁。
iptables -I INPUT -s 192.168.130.129 -j DROP
iptables -I INPUT -S 192.168.130.131 -j DROP
#解封。
iptables -D INPUT 1
iptables -D INPUT 1

也可以分别在node1和node3节点上执行封禁命令。

#封禁。
iptables -I INPUT -s 192.168.130.130 -j DROP
#解封。
iptables -D INPUT 1

如果集群的节点部署跨网段,可以采取禁用整个网络段的方式模拟网络分区。

假设rabbitmq集群中 3 个节点和其对应的 IP 关系如下:

node1 192.168.0.2
node2 192.168.1.3
node3 192.168.0.4

模拟出[node1,node3][node2]两个分区的情形,可以在 node2 节点上封禁命令。

#封禁。
iptables -I INPUT -s 192.168.0.0/24 -j DROP
#解封.
iptables -D INPUT 1

2.3. 封禁网卡

先使用ifconfig命令来查询出当前的网卡编号,如下所示,一般情况下单台机器只有-个网卡。

[root@node1 ~]# ifconfig

图

同样假设 node1、node2和node3这三个节点组成 RabbitMQ 集群,node2 的网卡编号为ens33,此时要模拟网络分区[nodel,node3][node2]的情形,需要在node2上执行封禁网卡命令。

#封禁。
ifdown ens33 或者 systemctl stop  network
#解封。
ifup ens33  或者 systemctl start  network

2.4. 挂起恢复操作系统

操作系统的挂起和恢复操作也会导致集群内节点的网络分区。因为发生挂起的节点不会认为自身已经失败或者停止工作,但是集群内的其他节点会这么认为。

如果集群中的一个节点运行在一台笔记本电脑上,然后你合上了笔记本电脑,那么这个节点就挂起了。或者一个更常见的现象,集群中的一个节点运行在某台虚拟机上,然后虚拟机的管理程序挂起了这个虚拟机节点,这样节点就被挂起了。在等待节点应答时间后,判定出net_tick_timeout,再恢复挂起的节点即可以复现网络分区。

3. 网络分区的影响

未配置镜像队列情况下:

  • 对于未配置镜像的集群,网络分区发生之后,队列也会伴随着宿主节点而分散在各自的分区之中。
  • 对于消息发送方而言,可以成功发送消息,但是会有路由失败的现象,要需要配合 mandatory 等机制保障消息的可靠性。
  • 对于消息消费方来说,有可能会有诡异不可预知的现象发生,比如对于已消费消息的 ack 会失效。如果网络分区发生之后,客户端与某分区重新建立通信链路,其分区中如果没有相应的队列进程,则会有异常报出。如果从网络分区中恢复之后,数据不会丢失,但是客户端会重复消费。

已配置镜像队列情况下:

  • 当镜像配置都是ha-sync-mode=automatic时,
  • 若有新的slave出现时,此slave会自动同步master中的数据。同步过程中,集群的整个服务都不可用,客户端连接会被阻塞。
  • 若master 中有大量的消息堆积,必然会造成slave的同步时间增长,进步影响了集群服务的可用性。
  • 当镜像配置都是ha-sync-mode=manual时,
  • 若有新的slave创建时,此slave不会去同步master上旧的数据,如果此时master节点又发生了异常,那么此部分数据将会丢失同样ha-promote-on-shutdown这个参数的影响也需要考虑进来。

4. 处理网络分区

4.1. 手动处理

恢复步骤:

  • 第一步,挑选一个信任分区。
  • 第二步,重启非信任分区中的节点,若重启后还有网络分区的告警,再紧接着重启信任分区中的节点。

挑选信任分区:

  • 挑选信任分区有几个指标,优先级从前到后为:
  • 分区中要有 disc 节点;
  • 分区中的节点数最多分区中的队列数最多;
  • 分区中的客户端连接数最多。

重启方式(2种方式):

  • 第一种:先rabbitmqctl stop命令关闭,再用 rabbitmq-server -detached 命令启动。
  • 第二种,先rabbitmqctl stop_app命令关闭,再用 rabbitmqctl start_app 命令启动。(推荐)

重启顺序(2种方式):

  • 第一种,先停止其他非信任分区中的所有节点,然后再启动这些节点。如果此时还有网络分区的告警,则再重启信任分区中的节点以去除告警。比如我上面这种网络分区情况,先关闭node2上的服务,在启动node2服务,若还存在网络分区告警,则在重启node1和node3节点。
  • 第二种,先关闭整个集群中的节点,然后再启动每一个节点,这里需要确保启动的第一个节点在信任的分区之中。

注意事项:

  • 在选择哪种重启顺序之前,首先考虑一下队列“漂移”的现象。所谓的队列“漂移”是在配置镜像队列的情况下才会发生的。
  • 一定要按照这里提及的重启方式重启。若选择挨个节点重启的方式,虽也可以处理网络分区,但是会出现Mnesia 内容权限的归属问题,而且还可能会引起二次网络分区的发生。比如有两个分区[node1,node2]和node3,node4],其中[node1,node2]为信任分区。此时若按照挨个重启的方式进行重启,比如先重启 node3,在 node3 节点启动之时无法判断其节点的 Mnesia 内容是向node1,node2]分区靠齐还是向 node4 节点靠齐。至此,如果挨个一轮重启之后,最终集群中的Mnesia 数据是[node3,node4]这个非信任分区,就会造成无法估量的损失。挨个节点重启。

4.1.1 镜像队列漂移现象

漂移现象:

  • 若存在镜像队列,从发生网络分区到恢复的过程中队列可能会出现“漂移”的现象。所谓漂移就是队列的master节点都“漂移”到一个节点上,除了发布消息之外,其他操作都在master节点上完成,从而造成负载不均衡。

缓解方法:

  • 可以重启之前先删除镜像队列的配置,这样能够在一定程度上阻止队列的“过分漂移”,即阻止可能所有队列都“漂移”到一个节点上的情况。

处理方法:

  • 第一种,最常见的就是直接在监控页面上删除。 图图

  • 第二种,若事先没有开启RabbitMQ Management插件,就只能使用rabbitmqctl工具的方式。但是需要在每个分区上都执行删除镜像队列配置的操作,以确保每个分区中的镜像都被删除。比如我这里就要在node1和node2节点上都要执行删除命令,或者在node3和node2节点上执行。

    • 查看命令:rabbitmqctl list_policies
    • 删除命令:rabbitmqctl clear_policy [-p vhost] {mirror_queue_name}
[root@node1 ~]# rabbitmqctl list_policies
[root@node1 ~]# rabbitmqctl clear_policy baimu_policy

图

4.1.2 处理流程总结

网终分区处理步骤总结如下:

  • 步骤 1: 先挂起生产者和消费者进程。这样可以减少消息不必要的丢失,若进程数过多情形又比较紧急,也可跳过此步骤。
  • 步骤 2:删除镜像队列的配置。
  • 步骤 3:挑选信任分区。
  • 步骤4:关闭非信任分区中的节点。采用rabbitmqctl stop_app命令关闭。
  • 步骤 5:启动非信任分区中的节点。采用与步骤4对应的rabbitmqctl start_app命令启动。
  • 步骤 6:检查网络分区是否恢复。若已经恢复,则转步骤8;若还有网络分区的报警,则转步骤7。
  • 步骤 7:重启信任分区中的节点。
  • 步骤 8:添加镜像队列的配置。
  • 步骤 9:恢复生产者和消费者的进程。

4.2 自动处理网络分区

rabbitmg.config配置文件中配置cluster_partition_handling参数即可实现相应的功能。有三种方法自动地处理网络分区:pause_minority模式、pause_if_all_down模式和autoheal模式。 默认是ignore模式,即不自动处理网络分区,所以在这种模式下,当网络分区的时候需要人工介入。

图

4.2.1 pause_minority 模式

cluster_partition_handling = pause_minority,当发生网络分区时,集群中的节点在观察到某些节点“down”掉的时候,会自动检测其自身是否处于“少数派”(分区中的节点小于或者等于集群中一半的节点数),rabbitmq会自动关闭少数派节点的rabbitmq应用,Erlang虚拟机不关闭。

根据CAP3原理,这里保障了P,即分区耐受性。这样确保了在发生网络分区的情况下,大多数节点(当然这些节点得在同一个分区中) 可以继续运行。“少数派”中的节点在分区开始时会关闭,当分区结束时又会启动。相当于执行了rabbitmgctl stop_app命令。处于关闭的节点会每秒检测一次是否可连通到剩余集群中,如果可以则启动自身的应用。相当于执行 rabbitmqctl start app 命令。

CAP 原理:

  • CPA原理,又称之为CAP定理,是指在一个分布式系统中,Consistency(一致性)、Availability(可用性)和Partition tolerance(分区耐受性) 三者不可兼得。

注意事项:

  • 若集群中只有两个节点时,不适合采用pause_minority模式。因为其中任何一个节点失败而发生网络分区时两个节点都会关闭。当网络恢复时,有可能两个节点会自动启动恢复网络分区,也有可能仍保持关闭状态。
  • 若集群节点数远大于2个时,采用pause_minority模式比 ignore 模式更加可靠,特别是网络分区通常是由单节点网络故障而脱离原有分区引起的。
  • 若出现对等分区时,会关闭这些分区内的所有节点,只有等待网络恢复之后,才会自动启动所有的节点以求从网终分区中恢复。比如集群组成为[node1,node2,node3,node4]四个节点,由于某种原因分裂成类似[node1,node2][node3,node4]这两个网络分区的情形。这种情况在跨机架部署时就有可能发生,当node1和node2 部署在机架 A 上,而 node3 和 node4 部署在机架 B 上,那么有可能机架A与机架B之间网络的通断会造成对等分区的出现。对于[node1,node2][node3,node4]而言,这四个节点上的rabbitmq应用都会被关闭。

4.2.2 pause_if_all_down 模式

cluster_partition_handling = pause_if_all_down,rabbitmq集群中的节点在和配置检查节点列表中的任何节点不能交互时才会关闭。

图

如上图配置表示:

  • 若一个节点与rabbitmq_1@node1rabbitmq_2@node2节点无法通信时,则会关闭自身的rabbitmq应用。
  • 若是rabbitmq_1@node1rabbitmq_2@node2本身发生了故障造成网络不可用,而其他节点都是正常的情况下,这种规则会让所有的节点中rabbitmq应用都关闭,待rabbitmq_1@node1rabbitmq_2@node2中的网络恢复之后,各个节点再启动自身应用以从网络分区中恢复。

注意事项:

  • pause_if_all_down模式下有 ignore 和 autoheal 两种配置。若出现对等分区时,node1和node2部署在机架A上,而node3和node4部署在机架B上。而配置里是node1和node3节点名称,那么当机架A 和机架B 的通信出现异常时,由于 nodel 和 node2 保持着通信,node3 和 node4 保持着通信,这 4 个节点都不会自行关闭,但是会形成两个分区,所以这样不能实现自动处理的功能。所以如果将配置中的 ignore替换成 autoheal 就可以处理此种情形。
  • autoheal模式下,如果集群中有节点处于非运行状态,那么当发生网络分区的时候,将不会有任何自动处理的动作。

autoheal 模式:

在 autoheal 模式下,当认为发生网络分区时,rabbitmq会自动决定一个获胜 (winning)的分区,然后重启不在这个分区中的节点来从网络分区中恢复。

获胜分区:

一个获胜的分区是指客户端连接最多的分区,若产生一个平局,即有两个或者多个分区的客户端连接数一样多,那么节点数最多的一个分区就是获胜分区。若此时节点数也一样多,将以节点名称的字典序来挑选获胜分区。

总结:

对于 pause-minority 模式,关闭节点的状态是在网络故障时,也就是判定出 net_tick_timeout之时,会关闭“少数派”分区中的节点,等待网络恢复之后,即判定出网络分区之后,启动关闭的节点来从网络分区中恢复。 autoheal 模式在判定出 net_tick_timeout之时不做动作,要等到网络恢复之时,在判定出网络分区之后才会有相应的动作,即重启非获胜分区中的节点。

4.2.3 各模式优缺点

每种模式优缺点:

  • ignore模式:发生网络分区时,不做任何动作,需要人工介入。
  • pause-minoriy模式:对于对等分区的处理不够优雅,可能会关闭所有的节点。一般情况下,可应用于非跨机架、奇数节点数的集群中。
  • pause-if-all-down模式:对于受信节点的选择尤为考究,尤其是在集群中所有节点硬件配置相同的情况下。此种模式可以处理对等分区的情形。
  • autoheal模式:可以处于各人情形下的网络分区。但是如果集群中有节点处于非运行状态,则此种模式会失效。

引用资料

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