RabbitMQ实现网络分区
- 网络分区的判断
- 应答时间范围
- 判定方式
- 日志方式
- 命令查看
- 监控页面提示
- API查看
- 模拟网络分区
- 封禁端口
- 封禁IP
- 封禁网卡
- 挂起恢复操作系统
- 网络分区的影响
- 未配置镜像队列情况下
- 已配置镜像队列情况下
- 处理网络分区
- 手动处理
- 恢复步骤
- 挑选信任分区
- 重启方式(2种方式)
- 重启顺序(2种方式)
- 注意事项
- 镜像队列漂移现象
- 漂移现象
- 缓解方法
- 处理方法
- 处理流程总结
- 自动处理网络分区
- pause_minority 模式
- CAP 原理
- 注意事项
- pause_if_all_down 模式
- autoheal 模式
- 获胜分区
- 总结
- 各模式优缺点
网络分区的意义:
rabbitmq集群
的网络分区的容错性不是很高,一般情况可以使用Federation或者Shovel
插件就可以解决广域网中的使用问题。不过即使是在局域网环境下,网络分区也不可能完全避免,网络设备(比如中继设备、网卡) 出现故障也会导致网络分区。极端情况下还会造成数据丢失,影响服务的可用性。- 当一个集群发生网络分区时,这个集群会分成两个部分或者更多,它们各自为政,互相都认为对方分区内的节点已经挂了,包括队列、交换器及绑定等元数据的创建和销毁都处于自身分区内,与其他分区无关。如果原集群中配置了镜像队列,而这个镜像队列又牵涉两个或者更多个网络分区中的节点时,每一个网络分区中都会出现一个master节点,对于各个网络分区,此队列都是相互独立的。当然也会有一些其他未知的、怪异的事情发生。
- 在一致性数据模型下,若出现网络波动或者网络故障等异常情况,会导致整个数据链的性能就会大大降低。例如有
A、B、C、D
四节点为一个数据链,倘若其中的C节点网络异常,那么整个A——> B——> C——>D——> A
的数据链就会被阻塞,继而相关服条也会被阻塞,这种情况下就需要将异常节点C剥离整个分区,以确保rabbitmq服务的可用性及可靠性。等待网络恢复之后,可以进行相应的处理来将此前的异常节点加入集群中。
网络分区的影响:
- 数据丢失;
- 服务阻塞不可用;
- 网络恢复后,网络分区状态仍然存在。
网络分区的判断
判断机制:
- abbitmq集群节点内部通信端口默认为25672,两两节点之间都会有信息交互。如果某节点出现网络故障,或者是端口不通,则会致使与此节点的交互出现中断,这里就会有个超时判定机制,继而判定网络分区。
超时判定机制
与net_ticktime参数
有关,该参数默认值为60秒。若超时,则会有net_tick_timeout
的信息报出。这个参数与heartbeat_time
有些区别,heartbeat_time
是指客户端与RabbitMQ服务
之间通信的心跳时间,针对5672端口而言。
节点之间的应答机制(判断流程):
rabbitmq
集群内部的每个节点之间会每隔四分之一的net_ticktime
计一次应答 (tick
)。若有任何数据被写入节点中,则此节点被认为已经被应答 (ticked
) 了。如果连续 4 次,某节点都没有被 ticked
,则可以判定此节点已处于down状态
,其余节点可以将此节点剥离出当前分区。
应答时间范围
如下图,将连续4次的tick时间记为T,那么T的取值范围为:0.75 * net_ticktime < T<1.25 * net_ticktime
。
图中每个节点代表一次tick判定的时间戳,在2个临界值0.75 * net_ticktime和1.25 * net_ticktime
之间可以连续执行4次的tick判定。默认情况下,在45s < T< 75s
之间会判定出net_tick_timeout
。
判定方式
日志方式
rabbitmq不仅会将队列、交换器及绑定等信息存储在Mnesia数据库中,而且许多围绕网络分区的一些细节也都和这个Mnesia的行为相关。
如果一个节点不能在下时间连上另一个节点,那么Mnesia
通常认为这个节点已经挂了,就算之后两个节点又重新恢复了内部通信,但是这两个节点都会认为对方已经挂了,Mnesia
此时认定了发生网络分区的情况。这些会被记录到RabbitMQ
的服务日志之中。
命令查看
通过命令rabbitmqctl cluster_status
可以看到集群相关信息。
- 当没有发生网络分区时,在
Network Partitions
这一块显示无。 - 当发生网络分区时,
Network Partitions
这一块会显示相关信息。
监控页面提示
也可以通过 Web 管理界面来查看。如果出现了下图这种告警,即发生了网络分区。
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"
模拟网络分区
模拟网络分区的方式有多种,主要分为以下3大类:
iptables封禁/解封
IP地址或者端口号。- 关闭/开启网卡。
- 挂起/恢复操作系统
封禁端口
若是通过防火墙屏蔽端口只能模拟出net_tick_timeout
,不能触发网络分区。
由于rabbitmq集群
内部节点通信端口默认为 25672,可以封禁25672端口方式来模拟出net_tick_timeout
。开启防火墙只能模拟触发节点的net_tick_timeout
,但是模拟不了节点的网络分区现象。
比如我这里集群有3个节点,node1、node2、node3
。此时我们要模拟 node2 节点
被剥离出当前分区的情形,即模拟[nodel,node3]和[node2]
两个分区。就在 node2节点
执行封禁端口命令。
- 封禁node2节点上的25672端口
[root@node2 ~]# iptables -A INPUT -p tcp --dport 25672 -j DROP
[root@node2 ~]# iptables -A OUTPUT -p tcp --dport 25672 -j DROP
- 此时可以查看node1和node3节点日志,显示node2已经触发了节点之间的应答机制。
- 接触node2节点上的25672封禁,此时才会判定node2节点发生了网络分区。
[root@node2 ~]# iptables -D OUTPUT 1
[root@node2 ~]# iptables -D INPUT 1
- 此时再去查看node1和node3节点日志,显示node2节点出现了网络分区。而node2节点上的日志显示,node1和node3节点已经掉线。
- 分别查看三个节点上集群状态
- 查看web监控页面。
封禁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
封禁网卡
先使用ifconfig命令
来查询出当前的网卡编号,如下所示,一般情况下单台机器只有一个网卡。
[root@node1 ~]# ifconfig
同样假设 node1、node2和node3
这三个节点组成 RabbitMQ 集群
,node2
的网卡编号为ens33
,此时要模拟网络分区[nodel,node3]和[node2]
的情形,需要在node2
上执行封禁网卡命令。
#封禁。
ifdown ens33 或者 systemctl stop network
#解封。
ifup ens33 或者 systemctl start network
挂起恢复操作系统
操作系统的挂起和恢复操作也会导致集群内节点的网络分区。因为发生挂起的节点不会认为自身已经失败或者停止工作,但是集群内的其他节点会这么认为。
如果集群中的一个节点运行在一台笔记本电脑上,然后你合上了笔记本电脑,那么这个节点就挂起了。或者一个更常见的现象,集群中的一个节点运行在某台虚拟机上,然后虚拟机的管理程序挂起了这个虚拟机节点,这样节点就被挂起了。在等待节点应答时间后,判定出net_tick_timeout
,再恢复挂起的节点即可以复现网络分区。
网络分区的影响
未配置镜像队列情况下
- 对于未配置镜像的集群,网络分区发生之后,队列也会伴随着宿主节点而分散在各自的分区之中。
- 对于消息发送方而言,可以成功发送消息,但是会有路由失败的现象,要需要配合
mandatory
等机制保障消息的可靠性。 - 对于消息消费方来说,有可能会有诡异不可预知的现象发生,比如对于已消费消息的
ack
会失效。如果网络分区发生之后,客户端与某分区重新建立通信链路,其分区中如果没有相应的队列进程,则会有异常报出。如果从网络分区中恢复之后,数据不会丢失,但是客户端会重复消费。
已配置镜像队列情况下
- 当镜像配置都是
ha-sync-mode=automatic
时, - 若有新的slave出现时,此slave会自动同步master中的数据。同步过程中,集群的整个服务都不可用,客户端连接会被阻塞。
- 若master 中有大量的消息堆积,必然会造成slave的同步时间增长,进步影响了集群服务的可用性。
- 当镜像配置都是
ha-sync-mode=manual
时, - 若有新的slave创建时,此slave不会去同步master上旧的数据,如果此时master节点又发生了异常,那么此部分数据将会丢失同样
ha-promote-on-shutdown
这个参数的影响也需要考虑进来。
处理网络分区
手动处理
恢复步骤
- 第一步,挑选一个信任分区。
- 第二步,重启非信任分区中的节点,若重启后还有网络分区的告警,再紧接着重启信任分区中的节点。
挑选信任分区
挑选信任分区有几个指标,优先级从前到后为:
- 分区中要有 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]
这个非信任分区,就会造成无法估量的损失。挨个节点重启。
镜像队列漂移现象
漂移现象
- 若存在镜像队列,从发生网络分区到恢复的过程中队列可能会出现“漂移”的现象。所谓漂移就是队列的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
处理流程总结
网终分区处理步骤总结如下:
- 步骤 1: 先挂起生产者和消费者进程。这样可以减少消息不必要的丢失,若进程数过多情形又比较紧急,也可跳过此步骤。
- 步骤 2:删除镜像队列的配置。
- 步骤 3:挑选信任分区。
- 步骤4:关闭非信任分区中的节点。采用
rabbitmqctl stop_app
命令关闭。 - 步骤 5:启动非信任分区中的节点。采用与步骤4对应的
rabbitmqctl start_app
命令启动。 - 步骤 6:检查网络分区是否恢复。若已经恢复,则转步骤8;若还有网络分区的报警,则转步骤7。
- 步骤 7:重启信任分区中的节点。
- 步骤 8:添加镜像队列的配置。
- 步骤 9:恢复生产者和消费者的进程。
自动处理网络分区
在rabbitmg.config配置
文件中配置cluster_partition_handling参数
即可实现相应的功能。有三种方法自动地处理网络分区:pause_minority模式
、pause_if_all_down模式
和autoheal模式
。 默认是ignore模式
,即不自动处理网络分区,所以在这种模式下,当网络分区的时候需要人工介入。
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应用
都会被关闭。
pause_if_all_down 模式
若cluster_partition_handling = pause_if_all_down
,rabbitmq集群
中的节点在和配置检查节点列表中的任何节点不能交互时才会关闭。
如上图配置表示:
- 若一个节点与
rabbitmq_1@node1或rabbitmq_2@node2
节点无法通信时,则会关闭自身的rabbitmq应用。 - 若是
rabbitmq_1@node1或rabbitmq_2@node2
本身发生了故障造成网络不可用,而其他节点都是正常的情况下,这种规则会让所有的节点中rabbitmq应用
都关闭,待rabbitmq_1@node1
或rabbitmq_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
之时不做动作,要等到网络恢复之时,在判定出网络分区之后才会有相应的动作,即重启非获胜分区中的节点。
各模式优缺点
ignore模式
:发生网络分区时,不做任何动作,需要人工介入。pause-minoriy模式
:对于对等分区的处理不够优雅,可能会关闭所有的节点。一般情况下,可应用于非跨机架、奇数节点数的集群中。pause-if-all-down模式
:对于受信节点的选择尤为考究,尤其是在集群中所有节点硬件配置相同的情况下。此种模式可以处理对等分区的情形。autoheal模式
:可以处于各人情形下的网络分区。但是如果集群中有节点处于非运行状态,则此种模式会失效。