RabbitMQ Network Partition

线上的rabbitmq集群经常无法访问,集群状态也不正常,查找到如下资料:

一. 环境概述

1. 3个rabbitmq node组成的集群镜像,当客户端无法连接或者连接rabbitmq超时的时候,集群的状态如下:

1
2
3
4
5
6
7
8
# rabbitmqctl cluster_status
Cluster status of node rabbit@controller02 ...
[{nodes,[{disc,[rabbit@controller01]},
         {ram,[rabbit@controller03,rabbit@controller02]}]},
 {running_nodes,[rabbit@controller03,rabbit@controller02]},
 {cluster_name,<<"rabbit@controller01">>},
 {partitions,[{rabbit@controller02,[rabbit@controller01]}]},
 {alarms,[{rabbit@controller03,[]},{rabbit@controller02,[]}]}]

可以看到partitions中有内容,发生了网络分区。

2. 查看网卡状态,发现有部分丢包

1
2
3
4
5
6
7
8
9
# ifconfig 
eno16780032: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.105.10  netmask 255.255.255.0  broadcast 192.168.105.255
        inet6 fe80::250:56ff:fe93:3c8b  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:93:3c:8b  txqueuelen 1000  (Ethernet)
        RX packets 2109838432  bytes 1014280621917 (944.6 GiB)
        RX errors 0  dropped 12153  overruns 0  frame 0
        TX packets 1799546189  bytes 1089416841077 (1014.5 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0 

3. rabbitmq日志中有如下 ERROR

1
2
=ERROR REPORT==== 25-Sep-2017::09:50:59 ===
Mnesia(rabbit@controller02): ** ERROR ** mnesia_event got {inconsistent_database, running_partitioned_network, rabbit@controller01}

二. 网络分区

RabbitMQ 会将 fabric 信息保存在 Erlang 的分布式数据库 Mnesia 中。而和网络分区相关的许多细节问题都和 Mnesia 的行为相关。

1. 网络分区的探测

Mnesia 判定某个 node 失效的根据是,如果其他 node 无法连接该 node 的时间达到 1 分钟以上(详情请参 考 net_ticktime 的 说明)。当这两个 node 恢复到能联系上的状态时,都会认为对端 node 已 down 掉了,此时 Mnesia 将会判定发生了网络分区。这种情况会被记录进 RabbitMQ 的日志文件中,如下:

1
2
=ERROR REPORT==== 15-Oct-2012::18:02:30 ===
Mnesia(rabbit@smacmullen): ** ERROR ** mnesia_event got{inconsistent_database,running_partitioned_network, hare@smacmullen} 

RabbitMQ node 会记录下在当前 node 运行期间是否发生过这个 event ,并会通过 rabbitmqctl cluster_status 命令和管理插件将该信息暴露出来。 在正常情况下,rabbitmqctl cluster_status 显示结果中的 partitions 部分为空列表 []:

1
2
3
4
5
6
7
# rabbitmqctl cluster_status

Cluster status of node rabbit@smacmullen ...
[{nodes,[{disc,[hare@smacmullen,rabbit@smacmullen]}]},
 {running_nodes,[rabbit@smacmullen,hare@smacmullen]}, 
 {partitions,[]}] 
...done. 

如果发生了网络分区,那么会有如下信息显示出来:

1
2
3
4
5
6
7
8
# rabbitmqctl cluster_status

Cluster status of node rabbit@smacmullen ...
[{nodes,[{disc,[hare@smacmullen,rabbit@smacmullen]}]},
 {running_nodes,[rabbit@smacmullen,hare@smacmullen]}, 
 {partitions,[{rabbit@smacmullen,[hare@smacmullen]}, 
              {hare@smacmullen,[rabbit@smacmullen]}]}] 
...done. 

2. 当网络分区发生时

当发生网络分区时,分区的两侧(或者多侧)均能够独立的进化,同时认为另外一侧已经处于不可用状态。其中 queue、binding、exchange 均能够在各个分区中创建和删除。而由于网络分区而被割裂的==镜像队列==,最终会演变成每个分区中产生一个 master ,并且每一侧==均能独立进行工作==。其他未定义和奇怪的行为也可能发生。
需要额外注意的是, 当网络分区的情况得到恢复后,上述问题仍旧存在,直到你采取行动进行修复。

3. 由于 挂起/恢复 而导致的分区

当我们谈及“网络”分区时,其真正的意思是指:在任何情况下,同一个集群中的 node 在没有 down 掉的情况下,相互之前的通信被中断的情况。除了网络失效导致的分区外,当挂起和恢复集群 node 所在机器的整个 OS ,同样能够导致分区的发生。这种情况下,被挂起的 node 并不认为自己已经失效了,或者被停掉了,但是同一集群中的其他 node 会认为是这样。
你能通过合上笔记本盖子的方式,挂起运行在笔记本上的集群中的一个 node ,但更常见的情况是由于虚拟机被监管程序挂起导致。尽管允许将 RabbitMQ 集群运行在虚拟化环境中,你需要确保 VM 不会被在运行中被挂起。需要注意的是,一些虚拟化技术特性,例如将 VM 从一个 host ==迁移==至另外一个 host 时,会导致 VM 被挂起。 由于挂起导致的网络分区,在恢复的时候行为是不对称的。被挂起的 node 将有可能不会认为其他 node 已经 down 掉,但是会被集群中的其他 node 看作 down 掉。这个行为对于 pause_minority 模式来说有特殊含义。

4. 从网络分区中恢复

为了从网络分区中恢复,首先要选择你最相信的一个分区。选中的分区将会作为“权威机构”被 Mnesia 使用。任何发生在未被选中分区中的变更将会丢失。 停止其他分区的所有 node ,之后再重新启动它们。当它们重新加入到集群中时,它们将会从受信分区恢复自身的状态。最后,你同样应该重启受信分区中的所有 node 以便清除警告信息。 一种更简单的方式是,停止整个集群,再==重启集群==。如果你是按照这种方式来恢复网络分区的,那么请确保你所启动的第一个 node 为受信分区中的 node 。

5. 自动处理网络分区

RabbitMQ 同样提供了三种方式来自动处理网络分区问题:pause-minority 模式,pause-if-all-down 模式和 autoheal 模式
pause-minority 模式中,一旦发现有其他 node 失效,RabbitMQ 将会自动停止“特定”集群中的所有 node ,只要确定该集群为少数派集群(即少于或等于半数 node 数)。可以看出,这种策略是选择了 CAP 理论中的分区容错性(P),而放弃了可用性(A)。这种策略保证了当发生网络分区时,最多只有一个分区中的 nodes 会继续工作。而处于少数派集群中的 node 将在分区发生的开始就被停止,在分区恢复后重新启动。
pause-if-all-down 模式中,RabbitMQ 会自动停止集群中的 node ,只要其无法与列举出来的任何 node 进行通信 。这和上一个模式比较接近,但是该模式允许管理员来决定根据哪些 node 做判定,而不直接取决于与上下文环境。例如,如果集群是由位于数据中心 A 的两个 node ,以及位于数据中心 B 的两个 node 构成的,并且两个数据中心之间的连接断开了,那么 pause-minority 模式会导致所有的 node 被停掉。而对于 pause-if-all-down 模式来说,如果管理员列举出来的 node 是数据中心 A 中的那两个 node ,那么将只有数据中心 B 里的两个 node 被停掉。需要注意的是,可能存在列举出来的多个 node 本身就处于无法通信的不同分区中:在这种情况下,将不会有任何 node 被停掉。这也就是为什么存在一个额外的 ignore/autoheal 参数来进一步指示如何从分区中恢复。
autoheal 模式中,RabbitMQ 将在发生网络分区时,自动决议出一个胜出分区,并重启不在该分区中的所有 node 。与 pause_minority 模式不同的是,autoheal 模式是在分区结束阶段(已经形成稳定的分区)是起作用,而不是在分区开始阶段(刚刚开始分区)。
胜出分区是获得最多客户端连接的那个分区(或者如果产生了平局,则选择拥有最多 node 的那个;如果仍旧是平局,则随机选择一个分区)。

你可以在配置文件中设置 cluster_partition_handling 项的值为上述任何值:

1
2
3
pause_minority
{pause_if_all_down, [nodes], ignore | autoheal}
autoheal

需要明确的一点是,允许 RabbitMQ 自行处理网络分区问题并不代表你可以认为该问题就不存在了。无论何时网络分区都会导致 RabbitMQ 集群产生问题。你只是在可能遇到何种层次的问题上面多了些选择。正如在本文开始处所说的,如果你打算基于不可靠连接接入 RabbitMQ 集群,你应该使用 federation 或 shovel 。

三. 恢复策略选择

  1. ignore - 要求你所在的网络环境非常可靠。例如,你的所有 node 都在同一个机架上,通过交换机互联,并且该交换机还是与外界通信的必经之路。 并且你不想因为集群中的任意 node 失效而导致集群停工,即使集群中有 node 真的失效(默认策略是ignore )。
  2. pause_minority - 你的网络环境可能没有那么可靠。例如,你在 EC2 上构建了一个横跨 3 个 AZs 的集群,并且你假定同一时刻最多只有一个 AZ 会失效。在这种场景下,你希望剩余的 2 个 AZs 能够继续工作,直到失效 AZ 恢复后,位于其中的 node 重新自动加入集群,并且不会造成任何混乱。
  3. autoheal - 你的网络环境可能是不可靠的。你会更加关心服务的可持续性,而非数据完整性。你可以构建一个包含 2 个 node 的集群。

综上这里我们选择 pause_minority 策略

四. 参考资料

Clustering and Network Partitions
RabbitMQ 之 Clustering 和 Network Partition(翻译)

一个默默无闻的工程师的日常
Built with Hugo
主题 StackJimmy 设计