知识库

理解因果集群法定人数(Quorum)和集群恢复

因果集群的若干主要操作要求大多数集群成员在线,即“多数仲裁”(Majority Quorum)。当因果集群失去多数仲裁时,它将失去写入能力,以及添加或移除集群成员的能力。

本文解释了这一行为背后的逻辑,以及它如何影响特定集群问题的恢复。

仲裁提交与数据持久性

当我们失去仲裁时,集群进入只读状态,不再拥有可以执行写入操作的领导者(Leader)。

这种行为是支撑因果集群的 Raft 协议的一部分,旨在确保数据持久性并维护数据库的 ACID 特性。

由于我们采用仲裁提交机制,当失去仲裁时,最坏的情况是:拥有最新数据的节点仲裁组全部离线,而任何在线节点都不包含最新的提交。

在恢复仲裁并确保集群反映最新提交的数据之前,允许写入操作将是一个错误。

仲裁投票加入/移除与集群恢复

集群成员的投票加入(Vote-in)和投票移除(Vote-out)也需要仲裁,这既影响集群规模的扩展,也影响集群恢复时的可用选项。

请注意,集群成员身份(Membership)与集群节点的在线/离线状态是不同的。一个节点可以是集群的成员,但同时处于不可访问或离线状态。

虽然不可访问或离线的节点可能会触发“投票移除”尝试以将其从集群成员中剔除,但“投票移除”操作有两个前提条件:

  1. 集群成员数量必须高于 causal_clustering.minimum_core_cluster_size_at_runtime

  2. 必须存在多数仲裁以通过投票。

正如集群规模扩展一文中所述,只要保持仲裁,集群可以平滑缩容,直至达到配置的 causal_clustering.minimum_core_cluster_size_at_runtime(默认为 3)。

随着集群规模的缩小(或扩大!),维持多数仲裁所需的在线节点数量会根据集群成员规模而改变。

当我们失去仲裁时,必须恢复那些因导致仲裁丢失而离线/无响应的节点,以便重新获得仲裁。请记住,尽管这些成员可能已离线,但它们仍被计为集群成员,因为它们从未因仲裁丢失而被投票移除。

我们无法通过添加新集群成员来恢复仲裁,因为投票加入新成员本身就需要仲裁。

虽然这看起来很受限制,因为我们不能简单地通过添加新节点来恢复正常运行,但这种行为确实保障了数据持久性。

如前一节所述,在最坏的情况下,当失去仲裁时,可能只有离线节点才包含最新的提交数据(归因于仲裁提交)。

如果我们错误地允许添加新节点以恢复仲裁,虽然可以恢复写入操作,但离线成员中的数据将会丢失。即使我们之后恢复了这些成员,在此期间产生的新提交也可能与丢失的数据产生冲突,导致数据重复或不一致,且没有明确的方法来解决这种冲突。

要求在提交和集群投票加入/移除操作中均必须满足仲裁,对于维护集群的数据持久性是必要的。

投票加入/移除操作的示例日志消息

Raft 成员操作会出现在调试日志中,以下是你可能会看到的一些示例。

我们使用了一个拥有多达 6 个核心成员的因果集群。对于此配置,我们分别为每个成员使用 5001 到 5006 的发现端口,以及 7001 到 7006 的 Raft 监听地址。

此时,我们有 4 个核心节点在线,对应节点 1、2、3 和 5。现在我们尝试启动节点 6。

在调试日志中,我们将看到多条调试消息,其中特别显示了新成员的发现以及将新成员添加到集群的(成功!)尝试。

2019-03-29 21:57:59.670+0000 INFO [o.n.c.d.CoreMonitor] Discovered core member at localhost:5006
2019-03-29 21:57:59.676+0000 INFO [c.n.c.d.SslHazelcastCoreTopologyService] Core topology changed {added=[{memberId=MemberId{416eca31}, info=CoreServerInfo{raftServer=localhost:7006, catchupServer=localhost:6006, clientConnectorAddresses=bolt://:7667,https://:7464,https://:7463, groups=[], database=default, refuseToBeLeader=false}}], removed=[]}
2019-03-29 21:57:59.676+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Target membership: [MemberId{1b45d851}, MemberId{cf17e1cf}, MemberId{2011b3fb}, MemberId{3e6dbf35}, MemberId{416eca31}]
...
2019-03-29 21:58:14.806+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Getting consensus on new voting member set [MemberId{2011b3fb}, MemberId{3e6dbf35}, MemberId{1b45d851}, MemberId{cf17e1cf}, MemberId{416eca31}]
2019-03-29 21:58:14.806+0000 INFO [o.n.c.c.c.m.RaftMembershipChanger] ConsensusInProgress{}
2019-03-29 21:58:14.813+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Appending new member set RaftMembershipState{committed=MembershipEntry{logIndex=5, members=[MemberId{2011b3fb}, MemberId{3e6dbf35}, MemberId{1b45d851}, MemberId{cf17e1cf}]}, appended=MembershipEntry{logIndex=6, members=[MemberId{2011b3fb}, MemberId{3e6dbf35}, MemberId{1b45d851}, MemberId{cf17e1cf}, MemberId{416eca31}]}, ordinal=7}

如果我们停止节点 6,我们将看到成员集相应发生变化,因为集群的其他部分投票将节点 6 移出了集群。

2019-03-29 22:09:40.191+0000 WARN [o.n.c.d.CoreMonitor] Lost core member at localhost:5006
2019-03-29 22:09:40.203+0000 INFO [c.n.c.d.SslHazelcastCoreTopologyService] Core topology changed {added=[], removed=[{memberId=MemberId{416eca31}, info=CoreServerInfo{raftServer=localhost:7006, catchupServer=localhost:6006, clientConnectorAddresses=bolt://:7667,https://:7464,https://:7463, groups=[], database=default, refuseToBeLeader=false}}]}
2019-03-29 22:09:40.203+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Target membership: [MemberId{1b45d851}, MemberId{cf17e1cf}, MemberId{2011b3fb}, MemberId{3e6dbf35}]
2019-03-29 22:09:40.204+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Getting consensus on new voting member set [MemberId{2011b3fb}, MemberId{3e6dbf35}, MemberId{1b45d851}, MemberId{cf17e1cf}]
2019-03-29 22:09:40.204+0000 INFO [o.n.c.c.c.m.RaftMembershipChanger] ConsensusInProgress{}
2019-03-29 22:09:40.209+0000 INFO [o.n.c.m.RaftOutbound] No address found for MemberId{416eca31}, probably because the member has been shut down.
2019-03-29 22:09:40.209+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Appending new member set RaftMembershipState{committed=MembershipEntry{logIndex=6, members=[MemberId{2011b3fb}, MemberId{3e6dbf35}, MemberId{1b45d851}, MemberId{cf17e1cf}, MemberId{416eca31}]}, appended=MembershipEntry{logIndex=7, members=[MemberId{2011b3fb}, MemberId{3e6dbf35}, MemberId{1b45d851}, MemberId{cf17e1cf}]}, ordinal=9}

在这两种情况下,你都可以看到成员变更都是作为共识提交(Consensus commits)来实现的。

运行时低于最小核心规模时的示例日志消息

在此场景中,对于一个大小为 3 的集群,节点 1、3 和 5 在线。我们将停止节点 3 并观察日志消息。

2019-03-29 22:12:20.280+0000 WARN [o.n.c.d.CoreMonitor] Lost core member at localhost:5003
2019-03-29 22:12:20.281+0000 INFO [c.n.c.d.SslHazelcastCoreTopologyService] Core topology changed {added=[], removed=[{memberId=MemberId{cf17e1cf}, info=CoreServerInfo{raftServer=localhost:7003, catchupServer=localhost:6003, clientConnectorAddresses=bolt://:7637,https://:7434,https://:7433, groups=[], database=default, refuseToBeLeader=false}}]}
2019-03-29 22:12:20.281+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Target membership: [MemberId{1b45d851}, MemberId{3e6dbf35}]
2019-03-29 22:12:20.281+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Not safe to remove member [MemberId{cf17e1cf}] because it would reduce the number of voting members below the expected cluster size of 3. Voting members: [MemberId{3e6dbf35}, MemberId{1b45d851}, MemberId{cf17e1cf}]

无法实现 2 个节点的目标成员规模,因为这将使投票成员数量低于运行时的集群规模 3。因此,尽管节点 3 已离线,但它无法从成员集中移除。多数仲裁规模保持为 2/3,由于仍有 2 个集群节点在线,我们得以维持仲裁。在日志中,我们可能会看到针对离线节点的连接尝试,因为它即使离线仍被视为集群成员。

如果我们再移除一个节点,我们将失去仲裁。

2019-03-29 22:23:05.055+0000 WARN [o.n.c.d.CoreMonitor] Lost core member at localhost:5005
2019-03-29 22:23:05.056+0000 INFO [c.n.c.d.SslHazelcastCoreTopologyService] Core topology changed {added=[], removed=[{memberId=MemberId{1b45d851}, info=CoreServerInfo{raftServer=localhost:7005, catchupServer=localhost:6005, clientConnectorAddresses=bolt://:7657,https://:7454,https://:7453, groups=[], database=default, refuseToBeLeader=false}}]}
2019-03-29 22:23:05.056+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Target membership: [MemberId{3e6dbf35}]
2019-03-29 22:23:05.056+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Not safe to remove members [MemberId{1b45d851}, MemberId{cf17e1cf}] because it would reduce the number of voting members below the expected cluster size of 3. Voting members: [MemberId{3e6dbf35}, MemberId{1b45d851}, MemberId{cf17e1cf}]

我们很可能会看到这伴随着一个降级(stepdown)事件,因为没有仲裁我们就无法拥有领导者或写入能力。

2019-03-29 22:23:20.100+0000 INFO [o.n.c.c.c.s.RaftState] Leader changed from MemberId{3e6dbf35} to null
...
2019-03-29 22:23:20.101+0000 INFO [o.n.c.c.r.RaftReplicator] Lost previous leader 'MemberId{3e6dbf35}'. Currently no available leader
2019-03-29 22:23:20.101+0000 INFO [c.n.c.d.SslHazelcastCoreTopologyService] Step down event detected. This topology member, with MemberId MemberId{3e6dbf35}, was leader in term 2, now moving to follower.

我们很可能会看到这伴随着针对两个离线集群成员的连接尝试,以及因仲裁不存在而导致的选举失败。

让我们看看此时尝试启动节点 6 会发生什么。请记住,节点 6 已经被投票移出集群,因此它不再被视为集群成员;且当前缺乏集群成员的仲裁,因此我们应该会看到加入尝试失败。

2019-03-29 22:29:41.003+0000 INFO [o.n.c.d.CoreMonitor] Discovered core member at localhost:5006
2019-03-29 22:29:41.004+0000 INFO [c.n.c.d.SslHazelcastCoreTopologyService] Core topology changed {added=[{memberId=MemberId{416eca31}, info=CoreServerInfo{raftServer=localhost:7006, catchupServer=localhost:6006, clientConnectorAddresses=bolt://:7667,https://:7464,https://:7463, groups=[], database=default, refuseToBeLeader=false}}], removed=[]}
2019-03-29 22:29:41.004+0000 INFO [o.n.c.c.c.m.RaftMembershipManager] Target membership: [MemberId{3e6dbf35}, MemberId{416eca31}]

拓扑结构和目标成员消息显示新节点已被检测到并希望加入集群,但我们没有看到关于新投票成员集的共识消息,也没有看到带有已提交 MembershipEntry 的新成员集追加操作。

由于我们没有看到共识消息或提交成员条目的消息,我们知道节点 6 无法成功加入集群。

如果我们恢复其中一个离线成员节点,并通过让大多数集群成员在线来重新建立仲裁,我们将看到共识和已提交成员条目的消息恢复,因为仲裁将允许集群再次开始投票加入和移除集群成员,并且仲裁规模将随着成员集的变化而相应改变。

neo4j-admin unbind 如何影响因果集群恢复

因果集群问题的若干恢复程序需要使用 neo4j-admin unbind,该操作会删除集群状态,通常与删除、覆盖(例如从恢复中)或移动实例上的 graph.db 结合执行。由于图数据正在被更改,集群状态不再反映存储状态,因此 unbind 操作是必要的。

理解这如何影响集群成员身份,以及在某些情况下如何影响集群恢复,这一点很重要。

已执行集群状态解绑(unbind)的节点,不能用于恢复集群的仲裁(从而恢复写入操作)。

当你执行 neo4j-admin unbind 时,由于该节点的集群状态被销毁,其在集群中的身份也随之销毁。当该节点重新上线时,它将拥有一个新的成员 ID,并作为集群的一个全新节点出现。

如果集群当前拥有多数仲裁在线,则不应产生负面后果,因为仲裁应允许新恢复的节点被投票加入集群。

但如果集群没有仲裁,则新恢复的节点无法被投票加入,也无法为稳定集群做出贡献。即使恢复节点的先前成员 ID 仍被计为集群成员(尚未被投票移出集群),该节点也无法重获其先前的身份,也无法冒充为当前成员。允许这种情况发生是不正确的。

这种行为是有意的,并且是 Raft 实现的一部分,有助于确保数据持久性。如果没有这种行为,可能会出现导致集群中数据丢失或数据冲突的场景。

当集群失去仲裁时,唯一的恢复方法是让当前的离线成员(未被投票移出者)之一重新加入集群,且该成员必须没有使用过 neo4j-admin unbind

如果仲裁已经丢失,并且在不使用 neo4j-admin unbind 的情况下无法使任何当前离线成员重新上线,那么你可能被迫退回到完整的集群恢复程序,这将需要使集群离线。

© . This site is unofficial and not affiliated with Neo4j, Inc.