ZooKeeper 是如何解决脑裂问题的?
ZooKeeper 主要通过 ZAB 协议(ZooKeeper Atomic Broadcast) 结合 过半机制(Quorum) 和 Epoch(纪元/时代) 概念来解决脑裂(Split-Brain)问题。
简单来说,ZooKeeper 宁可牺牲可用性(停止服务),也要保证数据的一致性(CP原则)。
以下是具体的解决机制详解:
1. 过半机制(Quorum / Majority)—— 物理层面的防线
这是防止脑裂最基础的数学保证。ZooKeeper 规定:任何事务的提交和 Leader 的选举,都必须得到集群中“过半数”节点的认可。
- 公式:
- 原理: 在一个集群中,不可能同时存在两个“过半数”的子集群。
- 假设集群有 5 台机器。过半数是 3。
- 如果网络断开,集群分裂成 A区(2台)和 B区(3台)。
- B区: 拥有 3 台机器(满足过半),可以选举出 Leader 并正常工作。
- A区: 只有 2 台机器(不满足过半),无法选举出 Leader,也无法处理写请求,这部分集群会自动进入不可用状态(只读或停止服务)。
结论: 物理上保证了同一时间只能有一个合法的 Leader 所在的集群在工作,从而避免了两个集群同时写入导致的数据冲突。
2. Epoch(纪元/任期)机制 —— 逻辑层面的防线
即使有过半机制,还存在一种特殊情况:“假死”的旧 Leader 复活。
场景:
- Leader A 发生了 Full GC 或者网络瞬断,导致与 Follower 失去联系。
- Follower 认为 Leader A 挂了,于是发起重新选举,选出了新的 Leader B(且获得了过半支持)。
- 此时,Leader A 恢复了(GC结束或网络恢复),它并不知道自己已经被废黜了,还认为自己是 Leader,并试图处理客户端的写请求。这就是所谓的“僵尸 Leader”。
ZooKeeper 如何解决?
ZooKeeper 引入了 zxid(ZooKeeper Transaction Id),这是一个 64 位的数字。
- 低 32 位: 单调递增的计数器。
- 高 32 位: 代表 Epoch(纪元)。
处理流程:
- 选举新 Leader 时: 新 Leader B 产生时,会生成一个新的 Epoch(比如从 Epoch 1 变为 Epoch 2)。
- 更新状态: 集群中过半的 Follower 会更新自己的 Epoch 为 2,并只听从 Epoch 2 的 Leader 指挥。
- 旧 Leader 复活: 旧 Leader A(处于 Epoch 1)恢复后,试图发送写命令给 Follower。
- 拒绝请求: Follower 发现 Leader A 发来的请求 Epoch 是 1,而自己已经是 Epoch 2 了。Follower 会拒绝该请求,并告知 Leader A:“你的朝代已经亡了”。
- 降级: Leader A 收到拒绝信息后,意识到自己过时了,于是自动降级为 Follower,并同步 Leader B 的数据。
3. 选举时的限制
在 Leader 选举阶段,ZooKeeper 也有策略防止数据丢失或脑裂后的数据回滚:
- 选谁做 Leader? 并不是谁先发起谁就当选。节点在投票时,会优先投票给 zxid 最大(数据最新)的节点。
- 这保证了新的 Leader 一定拥有旧集群中所有已经提交(Committed)的数据,避免了脑裂恢复后的数据不一致。
总结
ZooKeeper 解决脑裂的核心策略可以概括为:
- 不让两个 Leader 同时存在: 利用 过半机制,确保网络分区时,只有拥有多数派的那一边能工作,少数派直接“罢工”。
- 不让旧 Leader 捣乱: 利用 Epoch(纪元) 标识,给 Leader 颁发“身份证”。一旦新王登基(Epoch增加),旧王的命令立刻失效,防止“僵尸 Leader”导致的数据错乱。