RabbitMQ--扩展--04--网络分区原理

RabbitMQ–扩展–04–网络分区原理


1、摘要

网络分区是在使用RabbitMQ 时所不得不面对的一个问题,网络分区的发生可能会引起消息丢失或者服务不可用等。

可以简单地通过重启的方式或者配置自动化处理的方式来处理这个问题。

2、网络分区的意义

RabbitMQ 集群的网络分区的容错性并不是很高,一般都是使用Federation 或者Shovel 来解决广域网中的使用问题。不过即使是在局域网环境下,网络分区也不可能完全避免,网络设备(比如中继设备、网卡)出现故障也会导致网络分区。

当出现网络分区时,不同分区里的节点会认为不属于自身所在分区的节点都已经挂(down)了,对于队列、交换器、绑定的操作仅对当前分区有效。在RabbitMQ 的默认配置下,即使网络恢复了也不会自动处理网络分区带来的问题。

RabbitMQ 从3.1版本开始会自动探测网络分区,并且提供了相应的配置来解决这个问题。

当一个集群发生网络分区时,这个集群会分成两个部分或者更多,它们各自为政,互相都认为对方分区内的节点已经挂了,包括队列、交换器及绑定等元数据的创建和销毁都处于自身分区内,与其他分区无关。如果原集群中配置了镜像队列,而这个镜像队列又牵涉两个或者更多个网络分区中的节点时,每一个网络分区中都会出现一个master节点,对于各个网络分区,此队列都是相互独立的。当然也会有一些其他未知的、怪异的事情发生。当网络恢复时,网络分区的状态还是会保持,除非你采取了一些措施去解决它。如果你没有经历过网络分区,就不算真正掌握RabbitMQ。

网络分区带来的影响大多是负面的,极端情况下不仅会造成数据丢失,还会影响服务的可用性。

或许你不禁要问,既然网络分区会带来如此负面的影响,为什么RabbitMQ 还要引入网络分区的设计理念呢?其中一个原因就与它本身的数据一致性复制原理有关,我们知道,RabbitMQ采用的镜像队列是一种环形的逻辑结构,如图10-1 所示。

在这里插入图片描述

图10-1 中为某队列配置了4 个镜像,其中A 节点作为master 节点,其余B、C和D节点作为slave节点,4个镜像节点组成一个环形结构。假如需要确认(ack)一条消息,先会在A节点即master节点上执行确认命令,之后转向B节点,然后是C和D节点,最后由D将执行操作返回给A节点,这样才真正确认了一条消息,之后才可以继续相应的处理。这种复制原理和ZooKeeper的Quorum2 原理不同,它可以保证更强的一致性。在这种一致性数据模型下,如果出现网络波动或者网络故障等异常情况,那么整个数据链的性能就会大大降低。如果C节点网络异常,那么整个A→B→C→D→A的数据链就会被阻塞,继而相关服务也会被阻塞,所以这里就需要寻入网络分区来将异常的节点剥离出整个分区,以确保RabbitMQ服务的可用性及可靠性。等待网络恢复之后,可以进行相应的处理来将此前的异常节点加入集群中。

网络分区对于RabbitMQ本身而言有利有弊,读者在遇到网络分区时不必过于惊慌。许多情况下,网络分区都是由单个节点的网络故障引起的,且通常会形成一个大分区和一个单节点的分区,如果之前又配置了镜像,那么可以在不影响服务可用性,不丢失消息的情况下从网络分区的情形下得以恢复。

3、网络分区的判定

RabbitMQ 集群节点内部通信端口默认为25672,两两节点之间都会有信息交互。如果某节点出现网络故障,或者是端口不通,则会致使与此节点的交互出现中断,这里就会有个超时判定机制,继而判定网络分区。

对于网络分区的判定是与net_ticktime这个参数息息相关的,此参数默认值为60秒。注意与heartbeat time 的区别,heartbeat time 是指客户端与RabbitMQ服务之间通信的心跳时间,针对5672端口而言。

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

RabbitMQ不仅会将队列、交换器及绑定等信息存储在Mnesia数据库中,而且许多围绕网络分区的一些细节也都和这个Mnesia 的行为相关。如果一个节点不能在T时间连上另一个节点,那么Mnesi 通常认为这个节点己经挂了,就算之后两个节点又重新恢复了内部通信,但是这两个节点都会认为对方已经挂了,Mnesia此时认定了发生网络分区的情况。这些会被记录到RabbitMQ的服务日志之中,

除了通过查看RabbitMQ服务日志的方式,还有以下3种方法可以查看是否出现网络分区。

3.1、方法1

采用rabbitmqctl工具来查看,即采用rabbitmqctl cluster status 命令。通过这条命令可以看到集群相关信息,未发生网络分区时的情形举例如下:

[{nodes,[{disc,[rabbit@node1,rabbit@node2,rabbit@node3]}]},
{running nodes,[rabbit@node2,rabbit@node3,rabbit@node1]},
{cluster name,<< " rabbit@node1 " >>},
{partitio 口s,[] } ]

3.2、方法2

通过Web 管理界面的方式查看。如果出现了下图这种告警,即发生了网络分区。也推荐读者采用这种方式来检测是否发生了网络分区。

在这里插入图片描述

3.3、方法3

通过HTTP API 的方式调取节点信息来检测是否发生网络分区。比如通过curl 命令来调取节点信息:

curl -i -u root : root123 -H "content-type:application/json" -X GET http://localhost:15672/api/nodes

4、网络分区的模拟

正常情况下,很难观察到RabbitMQ 网络分区的发生。为了更好地理解网络分区,需要采取某些手段将其模拟出来,以便对其进行相应的分析处理,进而在实际应用环境中遇到类似情形,可以让你的处理游刃有余。往长远方面讲,也可以采取一些必要的手段去规避网络分区的发生,或者可以监控网络分区以及准备相关的处理预案。

4.1、模拟网络分区的方法

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

5、网络分区的影响

5.1、未配置镜像

综上所述,对于未配置镜像的集群,网络分区发生之后,队列也会伴随着宿主节点而分散在各自的分区之中。对于消息发送方而言,可以成功发送消息,但是会有路由失败的现象,需要需要配合mandatory等机制保障消息的可靠性。对于消息消费方来说,有可能会有诡异、不可预知的现象发生,比如对于己消费消息的ack会失效。

如果网络分区发生之后,客户端与某分区重新建立通信链路,其分区中如果没有相应的队列进程,则会有异常报出。如果从网络分区中恢复之后,数据不会丢失,但是客户端会重复消费。

5.2、己配置镜像

如果集群中配置了镜像队列,那么在发生网络分区时,情形比未配置镜像队列的情况复杂得多,尤其是发生多个网络分区的时候。

这里先简单地从3个节点分裂成2个网络分区的情形展开讨论。如前一节所述,集群中有node1、node2和node3这3个节点,分别在这些节点上创建队列queuel、queue2和queue3,并配置镜像队列。采用iptables的方式将集群模拟分裂成[node1,node3]和[node2]这两个网络分区。

在这里插入图片描述

网络分区的发生可能会引起消息的丢失,当然这点也有办法解决。

首先消息发送端要有能够处理Basic.Return 的能力。

其次,在监测到网络分区发生之后,需要迅速地挂起所有的生产者进程。

之后连接分区中的每个节点消费分区中所有的队列数据。

在消费完之后再处理网络分区。最后在从网络分区中恢复之后再恢复生产者的进程。整个过程可以最大程度上保证网络分区之后的消息的可靠性。

同样也要注意的是,在整个过程中会伴有大量的消息重复,消费者客户端需要做好相应的幂等性处理。当然也可以采用集群迁移,将所有旧集群的资源都迁移到新集群来解决这个问题。

6、手动处理网络分区

为了从网络分区中恢复,首先需要挑选一个信任分区,这个分区才有决定Mnesia内容的权限,发生在其他分区的改变将不会被记录到Mnesia中而被直接丢弃。

在挑选完信任分区之后,重启非信任分区中的节点,如果此时还有网络分区的告警,紧接着重启信任分区中的节点。

6.1、如何挑选信任分区?

  1. 挑选信任分区一般可以按照这几个指标进行
    1. 分区中要有disc 节点
    2. 分区中的节点数最多
    3. 分区中的队列数最多
    4. 分区中的客户端连接数最多
  2. 优先级从前到后
    1. 例如信任分区中要有disc节点: 如果有两个或者多个分区满足,则挑选节点数最多的分区作为信任分区,如果又有两个或者多个分区满足,那么挑选队列数最多的分区作为信任分区。依次类推,如果有两个或者多个分区对于这些指标都均等,那么随机挑选一个分区也不失为一良策。

6.2、如何重启节点?

6.2.1、方式1

  1. 使用rabbitmqctl stop 命令关闭
  2. 然后再用rabbitmq-server -detached 命令启动
  3. 这种方式需要同时重启Erlang虚拟机和RabbitMQ应用

6.2.2、方式2

  1. 使用rabbitmqctl stop_app 关闭
  2. 然后使用rabbitmqctl start_app 命令启动。
  3. 这种方式只是重启RabbitMQ 应用

6.2.3、推荐方式

上面两种方式都可以从网络分区中恢复,但是更加推荐方式2,包括下一节所讲述的自动处理网络分区的方式,其内部也是采用方式2进行重启节点。

6.3、重启的顺序有何考究?

6.3.1、重启顺序的2中方式

RabbitMQ 的重启顺序也比较讲究,必须在以下两种重启顺序中择其一进行重启操作

  1. 停止其他非信任分区中的所有节点,然后再启动这些节点。如果此时还有网络分区的告警,则再重启信任分区中的节点以去除告警。
  2. 关闭整个集群中的节点,然后再启动每一个节点,这里需要确保启动的第一个节点在信任的分区之中。
    在选择哪种重启顺序之前,首先考虑一下队列"漂移"的现象。所谓的队列"漂移"是在配置镜像队列的情况下才会发生的。

6.3.2、具体的网络分区处理步骤如下所述。

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

7、自动处理网络分区

RabbitMQ 提供了三种方法自动地处理网络分区

  1. pause-mmonty 模式
  2. pause-if-all-down 模式
  3. autoheal 模式
  4. 默认是ignore 模式,即不自动处理网络分区,所以在这种模式下,当网络分区的时候需要人工介入

在rabbitmq.config 配置文件中配置cluster partition handling参数即可实现相应的功能。默认的ignore模式的配置如下,注意最后有个点号:

在这里插入图片描述

7.1、pause-minority 模式

在pause-minority 模式下,当发生网络分区时,集群中的节点在观察到某些节点"down"的时候,会自动检测其自身是否处于"少数派" (分区中的节点小于或者等于集群中一半的节点数),RabbitMQ 会自动关闭这些节点的运作。

根据CAP3原理,这里保障了P,即分区耐受性。这样确保了在发生网络分区的情况下,大多数节点(当然这些节点得在同一个分区中〉可以继续运行。"少数派"中的节点在分区开始时会关闭,当分区结束时又会启动。

这里关闭是指RabbitMQ应用的关闭,而Erlang虚拟机并不关闭,类似于执行了rabbitmqctl stop_app命令。处于关闭的节点会每秒检测一次是否可连通到剩余集群中,如果可以则启动自身的应用。相当于执行rabbitmqctl start_app 命令。

pause-rninority 模式相应的配置如下:

在这里插入图片描述

需要注意的是,RabbitMQ 也会关闭不是严格意义上的大多数,比如在一个集群中只有两个节点的时候并不适合采用pause-minority的模式,因为其中任何一个节点失败而发生网络分区时,两个节点都会关闭。

当网络恢复时,有可能两个节点会自动启动恢复网络分区,也有可能仍保持关闭状态。然而如果集群中的节点数远大于2个时,pause_minority模式比ignore 模式更加可靠,特别是网络分区通常是由单节点网络故障而脱离原有分区引起的。

不过也需要考虑2v2、3v3这种被分裂成对等节点数的分区的情况。

所谓的2v2这种对等分区表示原有集群的组成为[node1,node2,node3,node4],由于某种原因分裂成类似[node1,node2]和[node3,node4]这两个网络分区的情形。这种情况在跨机架部署时就有可能发生,当node1和node2部署在机架A上,而node3和node4部署在机架B上,那么有可能机架A与机架B之间网络的通断会造成对等分区的出现。

上面我们阐述了如何模拟网络分区,并没有明确说明如何模拟对等的网络分区。可以在node1和node2上分别执行iptables命令去封禁node3和node4的IP。如果node1、node2和node3、node4处于不同的网段,那么也可以采用封禁网段的做法。更有甚者,可以将node1、node2部署到物理机A上的两台虚拟机中,然后将node3、node4部署到物理机B上的两台虚拟机中,之后切断物理机A与B之间的通信即可。

当对等分区出现时,会关闭这些分区内的所有节点,对于前面的[node1,node2]和[node3,node4]的例子而言,这四个节点上的RabbitMQ应用都会被关闭。只有等待网络恢复之后,才会自动启动所有的节点以求从网络分区中恢复。

7.2、pause-if-all-down模式

在pause-if-all-down模式下,RabbitMQ集群中的节点在和所配置的列表中的任何节点不能交互时才会关闭, 语法为{pause if all down, [nodes列表], ignore | autoheal) ,其中[nodes列表] 为前面所说的列表,也可称之为受信节点列表。参考配置如下:

在这里插入图片描述

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

注意到pause-if-all-down 模式下有ignore和autoheal两种不同的配置。

考虑前面pause-minority 模式中提及的一种情形,node1和node2部署在机架A上,而node3和node4部署在机架B上。此时配置{cluster partition handling,{pause if all down,[‘rabbit@node1’,‘rabbit@node3’],ignore}},那么当机架A和机架B的通信出现异常时,由于node1和node2保持着通信,node3和node4保持着通信,这4个节点都不会自行关闭,但是会形成两个分区,所以这样不能实现自动处理的功能。所以如果将配置中的ignore替换成autoheal就可以处理此种情形。

7.3、autoheal 模式

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

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

对于pause-minority模式,关闭节点的状态是在网络故障时,也就是判定出net_tick_timeout之时,会关闭"少数派"分区中的节点,等待网络恢复之后,即判定出网络分区之后,启动关闭的节点来从网络分区中恢复。

autoheal 模式在判定出net tick timeout 之时不做动作,要等到网络恢复之时,在判定出网络分区之后才会有相应的动作,即重启非获胜分区中的节点。

8、挑选哪种模式

有一点必须要清楚,允许RabbitMQ 能够自动处理网络分区并不一定会有正面的成效,也有可能会带来更多的问题。

网络分区会导致RabbitMQ 集群产生众多的问题,需要对遇到的问题做出一定的选择。就像本章开篇所说的,如果置RabbitMQ 于一个不可靠的网络环境下,需要使用Federation或者Shovel。就算从网络分区中恢复了之后,也要谨防发生二次网络分区。每种模式都有自身的优缺点,没有哪种模式是万无一失的,希望根据实际情形做出相应的选择,下面简要概论以下4 个模式。

8.1、ignore 模式

发生网络分区时,不做任何动作,需要人工介入。

8.2、pause-minority 模式

对于对等分区的处理不够优雅,可能会关闭所有的节点。一般情况下,可应用于非跨机架、奇数节点数的集群中。

8.3、pause-if-all-down 模式

  1. 对于受信节点的选择尤为考究,尤其是在集群中所有节点硬件配置相同的情况下。
  2. 此种模式可以处理对等分区的情形。

8.4、autoheal 模式

可以处于各个情形下的网络分区。
但是如果集群中有节点处于非运行状态,则此种模式会失效。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值