问题描述
RabbitMQ 在某次使用过程中,突然发现某些队列不可用了,在 Dashboard 上可以看到队列的列表,但是点击详情时会提示:
Not found.
The object you clicked on was not found; it may have been deleted on the server.
1)根据提示队列可能被删除,既然已经被删除了,那为什么 Dashboard 上又显示着,于是尝试重建,失败,提示同名队列已存在。
2)又考虑到 Dashboard 是通过 Nginx 转发的,可能存在转移字符没有处理,导致获取队列详情 API 传参不正确,于是绕过 Nginx,直接通过节点的 IP 进行模拟请求:
> curl -H 'authorization: Basic xxx' "http://rabbit@0001:15672/api/queues/test/queue?lengths_age=60&lengths_incr=5&msg_rates_age=60&msg_rates_incr=5&data_rates_age=60&data_rates_incr=5"
{"error":"Object Not Found","reason":"Not Found"}
同样的报错信息,说明转发没有问题。
3)由于 RabbitMQ 是集群部署的,从 rabbit@0001 没有获取信息,尝试用同样的参数请求集群中的另一个节点:
> curl -H 'authorization: Basic xxx' "http://rabbit@0002:15672/api/queues/test/queue?lengths_age=60&lengths_incr=5&msg_rates_age=60&msg_rates_incr=5&data_rates_age=60&data_rates_incr=5"
{"consumer_details":[],"arguments":{"x-queue-type":"classic"},"auto_delete":false, ...}
居然有返回,说明集群的数据产生了不一致的情况。最后发现原来 3 个节点的集群,有 2 个节点连接异常了,通过重启另外两个节点,问题得到解决。
表面上是因为集群节点状态不正常导致了问题的发生,但实际更深层的原因是因为集群产生了网络分区,使得原来一个集群分裂成了多个,这类情况在分布式系统中很常见,于是针对此问题,进行了更进一步的分析。
集群节点信息
rabbit@0001 |
rabbit@0002 |
rabbit@0003 |
确定集群网络分区
1)RabbitMQ Dashboard
Overview 页面下会有提示:Network partition detected
并且可以明确看到发生分区的节点:The nature of the partition is as follows
Node | Was partitioned from |
---|---|
rabbit@0001 | rabbit@0002 rabbit@0003 |
rabbit@0002 | rabbit@0003rabbit@0001 |
rabbit@0003 | rabbit@0001rabbit@0002 |
2)登录到服务器上,查看集群状态
> rabbitmqctl cluster_status
...
Network Partitions
# 发生网络分区的提示
Node rabbit@0001 cannot communicate with rabbit@0002, rabbit@0003
...
什么是集群网络分区?
集群网络分区问题,一般指当集群节点之间出现了网络故障时,两个(或更多)节点都可以独立演化,因为此时双方都认为对方已经崩溃了。这种情况又被称为脑裂,集群中队列、绑定关系、交换机均可以单独创建或删除。
例如 RabbitMQ 集群的一个节点,如果另一个节点在一段时间内(默认60秒)无法联络它,那么当前节点会确定与其对等的节点是否已经宕机。如果两个节点重新建立了联系,并且双方都认为对方已宕机,那么当前节点将确定为已经发生了分区。
RabbitMQ 集群可用于实现不同的目标:通过节点间数据镜像复制以提高数据的安全性、提高客户端操作的可用性、提高整体吞吐量等。如果集群成员节点之间的网络连接发生故障,会影响客户端操作的数据一致性和可用性(CAP 定理)。由于不同的应用程序对一致性有不同的要求,并且可以不同程度地容忍不可用,因此可以使用不同的分区处理策略。
从脑裂中恢复
要从脑裂中恢复,首先需要选择一个我们最信任的分区,该分区将成为系统状态(模式、消息)使用的权限;在其他分区上发生的任何更改都将丢失。
停止其他分区中的所有节点,然后重新启动它们。当他们重新加入集群时,他们将从受信任的分区恢复状态。
# 停止服务
> rabbitmqctl stop
# 启动服务
> rabbitmq-server -detached
# 查看服务状态
> rabbitmqctl status
分区处理策略
RabbitMQ 提供了三种自动处理网络分区的方法:
- pause-minority 模式
- pause-if-all-down 模式
- autoheal 模式
默认是 ignore 模式,不处理。
在 pause-minority 模式下,RabbitMQ 会在其他节点宕机后,自动暂停那些被判定为少数派的集群节点(即小于或等于集群节点总数的一半)。因此,它从 CAP 定理中选择了分区容忍而不是可用性。这可以确保在发生网络分区时,单个分区中的最多节点将继续运行。少数节点将在分区开始时立即暂停,并在分区结束时再次开始。这种配置可以防止脑裂,因此能够自动恢复。
在 pause-if-all-down 模式下,RabbitMQ 会自动暂停那些无法访问列表中任何节点的集群节点。换句话说,RabbitMQ 必须关闭所有列出的节点才能暂停集群节点。
在 autoheal 模式下,如果一个分区被认为发生了,RabbitMQ 会自动决定哪个分区获胜(获胜的分区是拥有最多客户端连接的分区),并重启所有不在这个分区中的节点。
总结
经过此次集群网络分区问题的深入排查,我对分布式系统的 CAP 定理有了更深的理解。
- Consistency(C):一致性
- Availability(A):可用性
- Partition Tolerance(P):分区容错性
我们知道分布式系统无法同时满足一致性、可用性和分区容错性这三个特性,往往需要舍弃其中一个以满足另外两个,但实际分区并不是想不想要的问题,而是始终会存在的,因此最终的结果就是我们只能选择 AP 或者 CP,P 是无法舍弃的。