赞
踩
网络分区是在使用RabbitMQ 时所不得不面对的一个问题,网络分区的发生可能会引起消息丢失或者服务不可用等。可以简单地通过重启的方式或者配置自动化处理的方式来处理这个问题。
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 l 的Quorum2 原理不同,它可以保证更强的一致性。在这种一致性数据模型下,如果出现网络波动或者网络故障等异常情况,那么整个数据链的性能就会大大降低。如果C 节点网络异常,那么整个A→B→ C→D→A 的数据链就会被阻塞,继而相关服务也会被阻塞,所以这里就需要寻|入网络分区来将异常的节点剥离出整个分区,以确保RabbitMQ 服务的可用性及可靠性。等待网络恢复之后,可以进行相应的处理来将此前的异常节点加入集群中。网络分区对于RabbitMQ 本身而言有利有弊,读者在遇到网络分区时不必过于惊慌。许多情况下,网络分区都是由单个节点的网络故障引起的,且通常会形成一个大分区和一个单节点的分区,如果之前又配置了镜像,那么可以在不影响服务可用性,不丢失消息的情况下从网络分区的情形下得以恢复。
RabbitMQ 集群节点内部通信端口默认为25672 ,两两节点之间都会有信息交互。如果某节点出现网络故障,或者是端口不通, 则会致使与此节点的交互出现中断, 这里就会有个超时判定机制,继而判定网络分区。
对于网络分区的判定是与net_ticktirne这个参数息息相关的,此参数默认值为60 秒。注意与heartbeat time 的区别,heartbeat tirne 是指客户端与RabbitMQ 服务之间通信的心跳时间,针对5672 端口而言。如果发生超时则会有net tick timeout 的信息报出。在RabbitMQ 集群内部的每个节点之间会每隔四分之一的net ticktirne 计一次应答( tick ) 。如果有任何数据被写入节点中,则此节点被认为已经被应答( ticked ) 了。如果连续4次,某节点都没有被ticked ,则可以判定此节点己处于" down" 状态, 其余节点可以将此节点剥离出当前分区。
RabbitMQ 不仅会将队列、交换器及绑定等信息存储在Mnesia 数据库中,而且许多围绕网络分区的一些细节也都和这个Mnes ia 的行为相关。如果一个节点不能在T 时间连上另一个节点,那么Mnesia 通常认为这个节点己经挂了, 就算之后两个节点又重新恢复了内部通信, 但是这两个节点都会认为对方已经挂了, Mnesia 此时认定了发生网络分区的情况。这些会被记录到RabbitMQ 的服务日志之中,
除了通过查看RabbitMQ 服务日志的方式,还有以下3 种方法可以查看是否出现网络分区。
第一种,采用rabbitmqctl 工具来查看,即采用rabbitmqctl cluster status 命令。通过这条命令可以看到集群相关信息, 未发生网络分区时的情形举例如下:
- [{nodes , [{disc , [rabbit@nodel , rabbit@node2 , rabbit@node3]}]} ,
- {running nodes , [rabbit@node2 , rabbit@node3 , rabbit@nodel]} ,
- {cluster name , << " rabbit@nodel " >>} ,
- {partitio 口s , [] } ]
第二种,通过Web 管理界面的方式查看。如果出现了图10-3 这种告警, 即发生了网络分区。也推荐读者采用这种方式来检测是否发生了网络分区。
第三种,通过HTTP API 的方式调取节点信息来检测是否发生网络分区。比如通过curl 命令来调取节点信息:
- curl -i -u root : root123 -H " content-type : application/json " -X GET http://
- localhost:15672/api/nodes
正常情况下,很难观察到RabbitMQ 网络分区的发生。为了更好地理解网络分区,需要采取某些手段将其模拟出来,以便对其进行相应的分析处理,进而在实际应用环境中遇到类似情形,可以让你的处理游刃有余。往长远方面讲,也可以采取一些必要的手段去规避网络分区的发生,或者可以监控网络分区以及准备相关的处理预案。
模拟网络分区的方式有多种, 主要分为以下3 大类:
综上所述,对于未配置镜像的集群,网络分区发生之后,队列也会伴随着宿主节点而分散在各自的分区之中。对于消息发送方而言,可以成功发送消息,但是会有路由失败的现象, 需要需要配合mandatory等机制保障消息的可靠性。对于消息消费方来说,有可能会有诡异、不可预知的现象发生,比如对于己消费消息的ack会失效。如果网络分区发生之后,客户端与某分区重新建立通信链路,其分区中如果没有相应的队列进程,则会有异常报出。如果从网络分区中恢复之后, 数据不会丢失, 但是客户端会重复消费。
如果集群中配置了镜像队列,那么在发生网络分区时,情形比未配置镜像队列的情况复杂得多,尤其是发生多个网络分区的时候。这里先简单地从3 个节点分裂成2 个网络分区的情形展开讨论。如前一节所述,集群中有nodel 、node2 和node3 这3 个节点,分别在这些节点上创建队列queuel 、queue2 和queue3 , 并配置镜像队列。采用iptables 的方式将集群模拟分裂成[node1, node3]和[node2]这两个网络分区。
网络分区的发生可能会引起消息的丢失,当然这点也有办法解决。首先消息发送端要有能够处理Basic . Return 的能力。其次,在监测到网络分区发生之后,需要迅速地挂起所有的生产者进程。之后连接分区中的每个节点消费分区中所有的队列数据。在消费完之后再处理网络分区。最后在从网络分区中恢复之后再恢复生产者的进程。整个过程可以最大程度上保证网络分区之后的消息的可靠性。同样也要注意的是,在整个过程中会伴有大量的消息重复,消费者客户端需要做好相应的罪等性处理。当然也可以采用7 .4节中的集群迁移,将所有旧集群的资源都迁移到新集群来解决这个问题。
为了从网络分区中恢复,首先需要挑选一个信任分区,这个分区才有决定Mnesia 内容的权限,发生在其他分区的改变将不会被记录到Mnesia 中而被直接丢弃。在挑选完信任分区之后,重启非信任分区中的节点,如果此时还有网络分区的告警,紧接着重启信任分区中的节点。
挑选信任分区一般可以按照这几个指标进行: 分区中要有disc 节点: 分区中的节点数最多:分区中的队列数最多;分区中的客户端连接数最多。优先级从前到后, 例如信任分区中要有disc节点: 如果有两个或者多个分区满足,则挑选节点数最多的分区作为信任分区;如果又有两个或者多个分区满足,那么挑选队列数最多的分区作为信任分区。依次类推, 如果有两个或者多个分区对于这些指标都均等,那么随机挑选一个分区也不失为一良策。
RabbitMQ 中有两种重启方式: 第一种方式是使用rabbitmqctl stop 命令关闭,然后再用rabbitmq-server -detached 命令启动; 第二种方式是使用rabbitmqctlstop app 关闭, 然后使用rabbitmqctl start app 命令启动。第一种方式需要同时重启Erlang 虚拟机和RabbitMQ 应用, 而第二种方式只是重启RabbitMQ 应用。两种方式都可以从网络分区中恢复,但是更加推荐使用第二种方式,包括下一节所讲述的自动处理网络分区的方式,其内部也是采用的第二种方式进行重启节点。
RabbitMQ 的重启顺序也比较讲究,必须在以下两种重启顺序中择其一进行重启操作:
在选择哪种重启顺序之前, 首先考虑一下队列" 漂移"的现象。所谓的队列"漂移"是在配置镜像队列的情况下才会发生的。
- 具体的网络分区处理步骤如下所述。
-
- 步骤1:挂起生产者和消费者进程。这样可以减少消息不必要的丢失,如果进程数过多,情形又比较紧急,也可跳过此步骤。
-
- 步骤2:删除镜像队列的配置。
-
- 步骤3:挑选信任分区。
-
- 步骤4:关闭非信任分区中的节点。采用rabbitmqctl stop app 命令关闭。
-
- 步骤5:启动非信任分区中的节点。采用与步骤4 对应的rabbitmqctl start app命令启动。
-
- 步骤6:检查网络分区是否恢复, 如果己经恢复则转步骤8 ; 如果还有网络分区的报警则转步骤7。
-
- 步骤7:重启信任分区中的节点。
-
- 步骤8:添加镜像队列的配置。
-
- 步骤9:恢复生产者和消费者的进程。
RabbitMQ 提供了三种方法自动地处理网络分区: pause-mmonty 模式、pause-lιall-down 模式和autoheal 模式。默认是ignore 模式,即不自动处理网络分区,所以在这种模式下,当网络分区的时候需要人工介入。在rabbitmq.config 配置文件中配置cluster partition handling参数即可实现相应的功能。默认的ignore 模式的配置如下,注意最后有个点号:
在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]这两个网络分区的情形。这种情况在跨机架部署时就有可能发生,当nodel 和node2 部署在机架A 上,而node3 和node4 部署在机架B 上,那么有可能机架A 与机架B 之间网络的通断会造成对等分区的出现。
在10.3 节中只阐述了如何模拟网络分区, 并没有明确说明如何模拟对等的网络分区。可以在nodel 和node2 上分别执行iptables 命令去封禁node3 和node4 的IP 。如果nodel 、node2和node3 、node4 处于不同的网段,那么也可以采用封禁网段的做法。更有甚者, 可以将nodel 、node2 部署到物理机A 上的两台虚拟机中, 然后将node3 、node4 部署到物理机B 上的两台虚拟机中,之后切断物理机A 与B 之间的通信即可。
当对等分区出现时, 会关闭这些分区内的所有节点,对于前面的[nodel ,node2]和[node3 ,node4] 的例子而言,这四个节点上的RabbitMQ 应用都会被关闭。只有等待网络恢复之后,才会
自动启动所有的节点以求从网络分区中恢复。
在pause-if-all-down 模式下, RabbitMQ 集群中的节点在和所配置的列表中的任何节点不能交互时才会关闭, 语法为{pause if all down , [nodes l , ignore I autoheal) ,其中[nodes] 为前面所说的列表,也可称之为受信节点列表。参考配置如下:
如果一个节点与rabbit@nodel 节点无法通信时,则会关闭自身的RabbitMQ 应用。如果是rabbit@nodel 本身发生了故障造成网络不可用,而其他节点都是正常的情况下, 这种规则会让所有的节点中RabbitMQ 应用都关闭,待rabbit@node 1 中的网络恢复之后,各个节点再启动自身应用以从网络分区中恢复。
注意到pause-if-all-down 模式下有ignore 和autoheal 两种不同的配置。考虑前面pause-minority 模式中提及的一种情形, nodel 和node2 部署在机架A 上,而node3 和node4 部署在机架B 上。此时配置{cluster partition handling , {pause if all down ,['rabbit@nodel' , ' rabbit@node3 '] , 工gnore}} ,那么当机架A 和机架B 的通信出现异常时,由于nodel 和node2 保持着通信, node3 和node4 保持着通信,这4 个节点都不会自行关闭,但是会形成两个分区,所以这样不能实现自动处理的功能。所以如果将配置中的ignore替换成autoheal 就可以处理此种情形。
在autoheal 模式下, 当认为发生网络分区时, RabbitMQ 会自动决定一个获胜(winning)的分区,然后重启不在这个分区中的节点来从网络分区中恢复。一个获胜的分区是指客户端连接最多的分区,如果产生一个平局,即有两个或者多个分区的客户端连接数一样多,那么节点数最多的一个分区就是获胜分区。如果此时节点数也一样多,将以节点名称的字典序来挑选获胜分区。
对于pause-minority 模式,关闭节点的状态是在网络故障时,也就是判定出net_tick_timeout之时,会关闭"少数派"分区中的节点,等待网络恢复之后,即判定出网络分区之后,启动关闭的节点来从网络分区中恢复。autoheal 模式在判定出net tick timeout 之时不做动作,要等到网络恢复之时,在判定出网络分区之后才会有相应的动作,即重启非获胜分区中的节点。
有一点必须要清楚, 允许RabbitMQ 能够自动处理网络分区并不一定会有正面的成效,也有可能会带来更多的问题。网络分区会导致RabbitMQ 集群产生众多的问题,需要对遇到的问题做出一定的选择。就像本章开篇所说的,如果置RabbitMQ 于一个不可靠的网络环境下,需要使用Federation 或者Shovel。就算从网络分区中恢复了之后,也要谨防发生二次网络分区。每种模式都有自身的优缺点,没有哪种模式是万无一失的,希望根据实际情形做出相应的选择,下面简要概论以下4 个模式。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。