RabbitMQ09-网络分区
1、网络分区的意义
- RabbitMQ集群的网络分区的容错性并不是很高,一般都是使用Federation或者Shovel来解决广域网中的使用问题。不过即使是在局域网环境下,网络分区也不可能完全避免,网络设备(比如中继设备、网卡)出现故障也会导致网络分区。
- 当出现网络分区时,不同分区里的节点会认为不属于自身所在分区的节点都已经挂(down)了,对于队列、交换器、绑定的操作仅对当前分区有效。
- 在RabbitMQ的默认配置下,即使网络恢复了也不会自动处理网络分区带来的问题。
- RabbitMQ从3.1版本开始会自动探测网络分区,并且提供了相应的配置来解决这个问题。
- 当一个集群发生网络分区时,这个集群会分成两个部分或者更多,它们各自为政,互相都认为对方分区内的节点已经挂了,包括队列、交换器及绑定等元数据的创建和销毁都处于自身分区内,与其他分区无关。如果原集群中配置了镜像队列,而这个镜像队列又牵涉两个或者更多个网络分区中的节点时,每一个网络分区中都会出现一个master节点,对于各个网络分区,此队列都是相互独立的。当然也会有一些其他未知的、怪异的事情发生。当网络恢复时,网络分区的状态还是会保持,除非你采取了一些措施去解决它。
- 如果你没有经历过网络分区,就不算真正掌握RabbitMQ。网络分区带来的影响大多是负面的,极端情况下不仅会造成数据丢失,还会影响服务的可用性。
- 为什么RabbitMQ还要引入网络分区的设计理念呢?其中一个原因就与它本身的数据一致性复制原理有关,RabbitMQ采用的镜像队列是一种环形的逻辑结构。如图10-1所示,如果某队列配置了4个镜像,其中A节点作为master节点,其余B、C和D节点作为slave节点,4个镜像节点组成一个环形结构。假如需要确认(ack)一条消息,先会在A节点即master节点上执行确认命令,之后转向B节点,然后是C和D节点,最后由D将执行操作返回给A节点,这样才真正确认了一条消息,之后才可以继续相应的处理。在这种一致性数据模型下,如果出现网络波动或者网络故障等异常情况,那么整个数据链的性能就会大大降低。如果C节点网络异常,那么整个A->B->C->D->A的数据链就会被阻塞,继而相关服务也会被阻塞,所以这里就需要引入网络分区来将异常的节点剥离出整个分区,以确保RabbitMQ服务的可用性及可靠性。等待网络恢复之后,可以进行相应的处理来将此前的异常节点加入集群中。
- 网络分区对于RabbitMQ本身而言有利有弊,在遇到网络分区时不必过于惊慌。许多情况下,网络分区都是由单个节点的网络故障引起的,且通常会形成一个大分区和一个单节点的分区,如果之前又配置了镜像,那么可以在不影响服务可用性,不丢失消息的情况下从网络分区的情形下得以恢复。
2、网络分区的判定
- RabbitMQ集群节点内部通信端口默认为25672,两两节点之间都会有信息交互。如果某节点出现网络故障,或者是端口不通,则会致使与此节点的交互出现中断,这里就会有个超时判定机制,继而判定网络分区。
- 对于网络分区的判定是与net_ticktime这个参数息息相关的,此参数默认值为60秒。
- 如果发生超时则会有net_tick_timeout的信息报出。
- 在RabbitMQ集群内部的每个节点之间会每隔四分之一的net_ticktime计一次应答(tick)。如果有任何数据被写入节点中,则此节点被认为已经被应答(ticked)了。如果连续4次,某节点都没有被ticked,则可以判定此节点己处于"down" 状态,其余节点可以将此节点剥离出当前分区。
- 将连续4次的tick时间记为T,那么T的取值范围为:0.75 * net_ticktime < T <1.25 * net_ticktime。在默认情况下,在45s < T < 75s之间会判定出net_tick_timeout。。图lO-2可以形象地描绘出这个取值范围的缘由。
- 注意与heartbeat_time的区别,heartbeat_time是指客户端与RabbitMQ服务之间通信的心跳时间,针对5672端口而言。
- RabbitMQ不仅会将队列、交换器及绑定等信息存储在Mnesia数据库中,而且许多围绕网络分区的一些细节也都和这个Mnesia的行为相关。如果一个节点不能在T时间连上另一个节点,那么Mnesia通常认为这个节点己经挂了,就算之后两个节点又重新恢复了内部通信,但是这两个节点都会认为对方已经挂了,Mnesia此时认定了发生网络分区的情况。
2.1、模拟网络分区
- 模拟网络分区的方式有多种,主要分为以下3大类:
- iptables封禁/解封IP地址或者端口号。
- 关闭/开启网卡。
- 挂起/恢复操作系统。
示例:iptables的方式
- 由于RabbitMQ集群内部节点通信端口默认为25672,可以封禁这个端口来模拟出"net_tick_timeout",然后再开启此端口让集群判定网络分区的发生。
- 整个RabbitMQ集群由3个节点组成,分别为hh14、hh13和hh12。此时要模拟hh12节点被剥离出当前分区的情形,可以在hh12上执行如下命令以封禁25672端口。
//封禁25672端口 ]# iptables -I OUTPUT -p tcp --sport 25672 -j DROP ]# iptables -I INPUT -p tcp --dport 25672 -j DROP
- 等待75秒(45s < T < 75s)之后以确保日志中出现net_tick_timeout之后,在开启25672端口。
//开启25672端口 ]# iptables -D OUTPUT 1 ]# iptables -D INPUT 1
- 此时,hh12节点与其他节点的内部通信己经恢复,如果此时查看集群的状态可以发现[hh13, hh14]和[hh12]己形成两个独立的分区。
2.2、查看网络分区
- 可以通过RabbitMQ的日志查看是否发生了网络分区(net_tick_timeout)。
]# grep 'timeout' /apps/rabbitmq/var/log/rabbitmq/rabbit@hh14.log 2022-07-14 18:47:28.054943+08:00 [info] <0.558.0> node rabbit@hh12 down: net_tick_timeout
- 可以通过web管理界面查看是否发生了网络分区。
- 可以通过HTTP API查看是否发生了网络分区,如果partitions不为空则为发生了网络分区。。
]# curl -u root:root -H "content-type:application/json" -XGET http://localhost:15672/api/nodes | jq | grep -A3 partitions "partitions": [ "rabbit@hh13", "rabbit@hh14" ], -- "partitions": [ "rabbit@hh12" ], "os_pid": "49305", -- "partitions": [ "rabbit@hh12" ], "os_pid": "49291",
3、网络分区的影响
3.1、未配置镜像
- 对于未配置镜像的集群,网络分区发生之后,队列也会伴随着宿主节点而分散在各自的分区之中。
- 对于消息发送方而言,可以成功发送消息,但是会有路由失败的现象,需要配合mandatory等机制保障消息的可靠性。
- 对于消息消费方来说,有可能会有诡异、不可预知的现象发生,比如对于己消费消息的ack会失效。如果网络分区发生之后,客户端与某分区重新建立通信链路,其分区中如果没有相应的队列进程,则会有异常报出。如果从网络分区中恢复之后,数据不会丢失,但是客户端会重复消费。
3.2、配置镜像
- 如果集群中配置了镜像队列,那么在发生网络分区时,情形比未配置镜像队列的情况复杂得多,尤其是发生多个网络分区的时候。
- ha-sync-mode=automatic时,当有新的slave出现时,slave会自动同步master中的数据。注意在同步的过程中,集群的整个服务都不可用,客户端连接会被阻塞。如果master中有大量的消息堆积,必然会造成slave的同步时间增长,进一步影响了集群服务的可用性。ha-sync-mode=manual,有新的slave创建的同时不会去同步master上旧的数据,如果此时master节点又发生了异常,那么此部分数据将会丢失。同样ha-promote-on-shutdown 这个参数的影响也需要考虑进来。
- 网络分区的发生可能会引起消息的丢失,当然这点也有办法解决。
- (1)消息发送端要有能够处理Basic.Return的能力。
- (2)在监测到网络分区发生之后,需要迅速地挂起所有的生产者进程。之后连接分区中的每个节点消费分区中所有的队列数据。在消费完之后再处理网络分区。
- (3)在从网络分区中恢复之后再恢复生产者的进程。
- 整个过程可以最大程度上保证网络分区之后的消息的可靠性。同样也要注意的是,在整个过程中会伴有大量的消息重复,消费者客户端需要做好相应的幂等性处理。
4、处理网络分区
- 处理网络分区有四种模式:ignore、autoheal、pause_minority、pause_if_all_down。
- 手动处理网络分区:ignore。RabbitMQ处理网络分区默认使用的模式。
- 自动处理网络分区:autoheal、pause_minority和pause_if_all_down。RabbitMQ提供的三种自动处理网络分区模式。
- 在rabbitmq.conf文件中配置cluster_partition_handling参数即可实现相应的功能。默认的ignore模式的配置如下:
cluster_partition_handling = ignore
4.1、手动处理网络分区
- 怎样解决网络分区问题?
- 首先需要挑选一个信任分区,这个分区会决定使用的Mnesia内容,发生在其他分区的改变将不会被记录到Mnesia中而被直接丢弃。
- 在挑选完信任分区之后,重启非信任分区中的节点,如果此时还有网络分区的告警,紧接着重启信任分区中的节点。
- 有3个要点需要仔细考量:
- 如何挑选信任分区?
- 何重启节点?
- 重启的顺序有何考究?
- 挑选信任分区一般可以按照下面几个指标进行(优先级从高到低):
- (1)分区中要有disc节点。
- (2)分区中的节点数最多。
- (3)分区中的队列数最多。
- (4)分区中的客户端连接数最多。
- 例如,信任分区中要有disc节点,如果有两个或者多个分区满足,则挑选节点数最多的分区作为信任分区。如果又有两个或者多个分区满足,那么挑选队列数最多的分区作为信任分区。依次类推, 如果有两个或者多个分区对于这些指标都均等,那么随机挑选一个分区。
- RabbitMQ中有两种重启方式:
- (1)使用rabbitmqctl stop命令关闭,然后用rabbitmq-server -detached命令启动。
- (2)使用rabbitmqctl stop_app关闭,然后用rabbitmqctl start_app命令启动。
- 第一种方式会同时重启Erlang虚拟机和RabbitMQ应用,而第二种方式只是重启RabbitMQ应用。两种方式都可以从网络分区中恢复,但是更加推荐使用第二种方式。
- RabbitMQ的重启也很重要,必须在以下两种重启顺序中择一种进行重启操作:
- (1)停止其他非信任分区中的所有节点,然后再启动这些节点。如果此时还有网络分区的告警,则再重启信任分区中的节点以去除告警。
- (2)关闭整个集群中的节点,然后再启动每一个节点,这里需要确保启动的第一个节点在信任的分区之中。
- 在选择哪种重启顺序之前,需要考虑一下队列"漂移"的现象。所谓的队列"漂移"是在配置镜像队列的情况下才会发生的。
- 如果配置了镜像队列,从网络分区到恢复的过程中队列可能会出现"漂移"的现象。可以重启之前先删除镜像队列的配置,这样能够在一定程度上阻止队列的"过分漂移",即阻止可能所有队列都"漂移"到一个节点上的情况。
- 在每个分区上都执行删除镜像队列配置的操作,以确保每个分区中的镜像都被删除。删除镜像队列的配置可以采用rabbitmqctl工具删除:
rabbitmqctl clear_policy [--vhost vhost] {mirror_queue_name}
- 处理网络分区的具体步骤:
- (1)挂起生产者和消费者进程。这样可以减少消息不必要的丢失,如果进程数过多,情形又比较紧急,也可跳过此步骤。
- (2)删除镜像队列的配置。
- (3)挑选信任分区。
- (4)关闭非信任分区中的节点。采用rabbitmqctl stop_app命令关闭。
- (5)启动非信任分区中的节点。采用与步骤4对应的rabbitmqctl start_app命令启动。
- (6)检查网络分区是否恢复,如果己经恢复则转步骤8;如果还有网络分区的报警则转步骤7。
- (7)重启信任分区中的节点。
- (8)添加镜像队列的配置。
- (9)恢复生产者和消费者的进程。
4.2、自动处理网络分区
- RabbitMQ提供了三种自动处理网络分区的模式:pause_minority模式、pause_if_all_down模式和autoheal模式。
4.2.1、pause_minority模式
- pause-rninority模式的配置:
cluster_partition_handling = pause_minority
- 在pause_minority模式下,当发生网络分区时:
- 集群在观察到某些节点"down"的时候,每个RabbitMQ节点都会自动检测自身是否处于"少数派"(分区中的节点小于或者等于集群中一半的节点数),如果是,就会停止自身RabbitMQ应用的运行(只停止RabbitMQ应用,不停止Erlang虚拟机。相当于执行rabbitmqctl stop_app命令)。
- 处于停止状态的RabbitMQ节点会每秒检测一次是否可连通到"多数派"集群中,如果可以,就启动自身的RabbitMQ应用(相当于执行rabbitmqctl start_app命令)。
- 根据CAP原理,这里保障了P,即分区耐受性。这样确保了在发生网络分区的情况下,大多数节点(当然这些节点得在同一个分区中〉可以继续运行。"少数派"中的节点在分区开始时会关闭,当分区结束时又会启动。
- CAP原理又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)和Partition tolerance(分区耐受性)三者不可兼得。
- 需要注意的是,RabbitMQ也会关闭不是严格意义上的大多数,即当发生网络分区时,每个分区的节点数相等(对等分区)。
- 当对等分区出现时,会关闭所有分区内的所有节点,
- 如果集群中的节点数远大于2个时,pause_minority模式比ignore模式更加可靠,特别是网络分区通常是由单节点网络故障而脱离原有分区引起的。
4.2.2、pause_if_all_down模式
- pause_if_all_down模式的配置:
cluster_partition_handling = pause_if_all_down //恢复策略可以是'autoheal'或者'ignore' cluster_partition_handling.pause_if_all_down.recover = ignore //受信的节点列表 cluster_partition_handling.pause_if_all_down.nodes.1 = rabbit@myhost1 cluster_partition_handling.pause_if_all_down.nodes.2 = rabbit@myhost2
- 在pause_if_all_down模式下,RabbitMQ集群中的节点在和受信的节点列表中的所有节点都不能交互时才会关闭。
- 如果一个节点与rabbit@myhost1、rabbit@myhost2这两个节点都无法通信时,才会关闭自身的RabbitMQ应用。如果是rabbit@myhost1、rabbit@myhost2发生了故障造成网络不可用,而其他节点都是正常的情况下,这种规则会让所有的节点中RabbitMQ应用都关闭,待rabbit@myhost1(rabbit@myhost2或者两者)中的网络恢复之后,各个节点再启动自身应用以从网络分区中恢复。
- 注意到pause_if_all_down模式下有ignore和autoheal两种不同的配置。考虑一种情形,node1和node2部署在机架A上,而node3和node4部署在机架B上。此时受信任的节点配置的是rabbit@node1和rabbit@node2。那么当机架A和机架B的通信出现异常时,由于node1和node2保持着通信,node3和node4保持着通信,这4个节点都不会自行关闭,但是会形成两个分区,所以这样不能实现自动处理的功能。所以如果将配置中的ignore替换成autoheal就可以处理此种情形。
4.2.3、autoheal模式
- 在autoheal模式下,当发生网络分区时,RabbitMQ会自动选取一个获胜(winning)的分区,然后重启不在这个分区中的节点来从网络分区中恢复。
- 怎么选择获胜分区(优先级从高到低):
- (1)客户端连接最多的分区。
- (2)节点数最多的分区。
- (3)字典序排序靠前的分区。
- 例如,选择客户端连接最多的分区。如果有两个或者多个分区的客户端连接数一样多,那么就选择节点最多的分区。如果有两个或者多个分区的客户端连接和节点数一样多,那么就选择节点名称的字典序排序考前的分区。
- pause_minority模式,关闭节点的状态是在网络故障时,也就是判定出net_tick_timeout之时,会关闭"少数派"分区中的节点。等待网络恢复之后,即判定出网络分区之后,启动关闭的节点从而从网络分区中恢复。
- autoheal模式在判定出net_tick_timeout之时不做任何动作,要等到网络恢复之时,在判定出网络分区之后才会有相应的动作,即重启非获胜分区中的节点。
- 注意,在autoheal模式下,如果集群中有节点处于非运行状态,那么当发生网络分区的时候,将不会有任何自动处理的动作。
4.3、选择哪种模式
- 允许RabbitMQ自动处理网络分区并不一定会有正面的效果,也有可能会带来更多的问题。网络分区会导致RabbitMQ集群产生众多的问题,需要对遇到的问题做出一定的选择。
- 如果置RabbitMQ于一个不可靠的网络环境下,需要使用Federation或者Shovel。就算从网络分区中恢复了之后,也要谨防发生二次网络分区。
- 每种模式都有自身的优缺点,下面简要概论4个模式:
- ignore模式:发生网络分区时,不做任何动作,需要人工介入。
- pause_minority模式:对于对等分区的处理不够优雅,可能会关闭所有的节点。一般情况下,可应用于非跨机架、奇数节点数的集群中。
- pause_if_all_down模式:对于受信节点的选择尤为考究,尤其是在集群中所有节点硬件配置相同的情况下。此种模式可以处理对等分区的情形。
- autoheal模式:可以处理各个情形下的网络分区。但是如果集群中有节点处于非运行状态,则此种模式会失效。