集群与网络分区
本指南已针对 RabbitMQ 4.3.0 及更高版本进行了大幅修订。
简介
在 RabbitMQ 4.3.0 之前,本指南涵盖了 集群 的一个特定方面:节点间的网络故障、它们对 Raft 集群多数派的影响,以及恢复过程。
从 RabbitMQ 4.3.0 开始,一些重要细节发生了变化,使得本指南的篇幅大幅缩短。
- RabbitMQ 现在仅支持一种 元数据 存储:基于 Raft 的 Khepri。
- Mnesia 已被移除。
- Mnesia 时代所必需的分区处理策略也已被移除。
这意味着从 4.3.0 开始,RabbitMQ 中所有关键的复制特性(组件)均基于 Raft:
- 元数据存储 (Khepri)
- 仲裁队列 (Quorum queue)
- 流 (Stream) 协调器
在 Tanzu RabbitMQ 中,基于 Raft 的特性列表还包括:
- 延迟队列 (Delayed Queues)
- JMS 队列
有关这些特性的故障恢复特征的更详细说明,请参阅相应的文档指南。
如何发现网络分区
节点观察到的分区将被 记录到日志中,Raft 领导者选举活动亦会如此。
rabbitmq-diagnostics cluster_status CLI 命令 也会列出分区信息。
分区对复制特性的影响
除了 节点级心跳 外,基于 Raft 的特性还使用了一种 自适应故障检测器。与基于心跳的机制相比,它能在更早的时间发现对端不可用,同时对延迟抖动的敏感度更低。
网络分区会阻碍两个节点按正常方式通信。对于仲裁队列、流或元数据存储等 Raft 集群,存在三种关键场景:
- 托管当前已选 Raft 集群领导者的节点不可用
- 托管从属节点的节点不可用
- 未托管领导者也未托管从属节点的节点不可用
场景 1:领导者断开连接
如果当前已选的领导者变得不可用,其余成员将注意到此故障,并随即在分区的多数派一侧触发新的领导者选举:即那些仍能相互连接并形成 多数派 的集群成员组。
在 Raft 集群没有已选领导者的这段时间窗口内,写入操作将被拒绝,直到选出新的领导者为止。对于等待 发布者确认 的消息,这意味着可能会收到否定应答。
客户端对复制队列和流的操作会被延迟,直到选出新领导者并告知客户端所连接(曾连接)的节点。
从开始到结束,这通常需要几秒钟,具体取决于节点和网络负载。
新领导者将继续接受写入、处理读取,将状态(Raft 日志)复制给从属节点,并像断开连接前一样发送发布者确认。
已发送但 尚未确认 的消息将被重新放入队列,并由新选出的领导者稍后重新投递。在这种情况下,由于领导者断开连接和重新选举,消费者可能会多次收到同一条消息。
三种复制组件的读取可用性各不相同。
部分 Khepri 读取操作由本地缓存(默认的最终一致性路径)提供服务;这些操作在所有可达节点上均可继续进行。使用线性一致性读取的 Khepri 操作子集(需经过 Raft 集群领导者)会暂停,直到选出新领导者。
仲裁队列的投递始终经过领导者,因此在没有已选领导者的时间窗口内,其消费者不会看到新的投递。
流消费者可以继续从任何可达的副本读取已复制的数据,不受领导者变更的影响。
场景 2:从属节点断开连接
重新连接的 Raft 成员(副本)将发现当前已选的领导者,并从领导者处接收缺失的日志条目。实际上,这意味着重新连接的成员将利用集群领导者更新其本地状态,并获得 Raft 的一致性和数据安全保证。
在多数派一侧,读取正常进行。在断开连接的一侧,Khepri 提供本地缓存读取,仲裁队列投递暂停,流消费者仍可读取本地可用数据。
一旦同步完成,从属副本将像分区事件发生前一样继续运行。
场景 3:非成员节点断开连接
如果一个不托管任何仲裁队列或流成员(副本)的节点变得不可用,其断开连接仅影响集群容量,而不影响数据安全或更广泛的可用性。
受影响的仲裁队列或流的读取不受影响。连接到断开节点上的客户端必须重新连接到可达的集群节点。
Khepri 在每个已连接的集群节点上都有一个副本,因此该场景不适用于它。
分区恢复
断开连接的节点会尝试重新连接到其对等节点。这可能需要一些时间。
重新连接的 Raft 成员(副本)将发现当前已选的领导者,并从领导者处接收缺失的日志条目。实际上,这意味着重新连接的成员将利用集群领导者更新其本地状态,并获得 Raft 的一致性和数据安全保证。
如果连接中断时间较短,通常意味着传输的数据量相对较小(请根据集群中的数据量和速度调整“较小”的概念)。
如果中断时间较长,数据量可能会很大;在返回的成员追赶进度时,应将其视为暂时不可用。在完全追赶上进度之前,此类节点也无法被选举为 Raft 集群的领导者。
Raft 与在线集群多数派要求
Raft 的数据安全和可预测的恢复是有运维代价的:任何时刻都必须有多数成员(副本)在线且可达。
如果情况并非如此,写入操作(入队、注册确认、元数据存储写入)将被拒绝。
不同规模 Raft 集群的故障容错特性可以用表格来描述。注意,该表针对 Khepri 指的是集群节点,但对于仲裁队列和流,它指的是它们所拥有的副本数量(这可能是所有集群节点的一个子集)。
| 集群节点数 | 容忍的节点故障数 | 容忍网络分区 |
|---|---|---|
| 1 | 0 | 不适用 |
| 2 | 0 | 否 |
| 3 | 1 | 是 |
| 4 | 1 | 是(如果其中一边存在多数派) |
| 5 | 2 | 是 |
| 6 | 2 | 是(如果其中一边存在多数派) |
| 7 | 3 | 是 |
| 8 | 3 | 是(如果其中一边存在多数派) |
| 9 | 4 | 是 |
由挂起和恢复引起的分区
虽然本指南讨论的是“网络”分区,但从更广泛的意义上讲,分区是指集群中的不同节点在没有任何节点实际崩溃的情况下通信被中断的任何场景。
除了网络故障外,对运行中的集群节点进行整个操作系统的挂起和恢复,也会导致分区。因为被挂起的节点认为自己没有发生故障,甚至没有停止,但集群中的其他节点会认为它已经停止了。
发生这种情况最常见的原因是虚拟机被宿主机挂起。某些虚拟化特性(例如将虚拟机从一台主机迁移到另一台主机)往往涉及虚拟机的挂起。
由挂起和恢复引起的分区应被视为影响单个节点的任何其他分区类型。然而,得益于自适应统计故障检测器,短暂的挂起可以得到妥善处理。
所有基于 Raft 的特性都能够以标准的安全性保证和在线多数派可用性要求来处理这些挂起并恢复的成员。
得益于 Raft 的增量日志恢复功能,挂起后恢复的节点只需检索日志的子集,从而显著减少了恢复(不可用)的时间窗口。
已弃用的 rabbitmq.conf 键
为了向后兼容,以下与旧版本分区处理策略配置相关的 rabbitmq.conf 键仍被接受,但不起任何作用:
cluster_partition_handlingcluster_partition_handling.pause_if_all_down.recovercluster_partition_handling.pause_if_all_down.nodes.$name
应尽快从配置文件中删除这些键。