18.6 脑裂与防护

一句话

脑裂就是 “两个节点都以为自己是主”:分区期间各自接受写入,恢复后数据分叉;防护只有三条——quorum(没有多数派不许写)、fencing(旧主写不进去)、单点仲裁,缺一条都不算防住。

实测:没有 quorum 会发生什么

3 节点分成 2+1 两个分区:

策略 接受写入 分歧 key
无 quorum 300 条(两边都写) 100 个
有 quorum(多数派才可写) 200 条(少数派拒绝) 0 个

差别不在 “写入多少”,而在 “恢复后要不要人工合并”:300 条里有 100 个 key 出现两个值,合并意味着要么丢数据、要么人工裁决。quorum 用 “暂时不可用” 换 “永不冲突”。

可写条件:节点数 ≥ ⌈(N+1)/2⌉
3 节点 → 至少 2 个;5 节点 → 至少 3 个
少数派分区:只读或拒绝写(取决于业务能否容忍)

三道防线

防线 做法 防住什么
quorum 写前确认多数派在线 分区时少数派不能写
fencing 存储侧只接受 “最新 token” 的写 旧主复活后写不进去
仲裁者(witness) 第 3 个轻量节点参与投票 2 节点集群避免 1:1 平票
// 写入前校验:拿不到多数派就拒绝(宁可不可用,不可分叉)
if len(alive) < (n+1)/2 {
    return ErrNoQuorum          // 客户端应降级为只读
}
// 存储侧:只接受更大的 token(防旧主)
if token < lastToken { return ErrStaleOwner }

反模式

反模式 后果
异步复制 + 自动切换,无 quorum 分区时双主,数据分叉
只有 quorum,没有 fencing 旧主 “复活” 后仍能写(假死场景)
2 节点集群无仲裁 1:1 平票,谁都选不出主
少数派继续接受写 恢复后必须人工合并,通常丢数据
用 “心跳恢复” 判断 “可以合并” 心跳恢复 ≠ 数据一致
不做双主检测告警 分叉可能持续很久才被发现

三条铁律

🔑 没有多数派就不许写;可用性让位于一致性。

🔑 fencing 是最后一道闸;quorum 挡不住 “假死复活”。

🔑 双主是事故,不是配置;发现即告警、即止血。

自检

Q1 为什么 “两边都能写” 比 “少数派只读” 更糟?

▸ 答案:因为两边都能写会产生同一 key 的两个值,恢复后无法自动判定谁对——要么丢数据、要么人工裁决。少数派只读只是 “暂时不可用”,恢复后数据仍然一致。前者是数据事故,后者是可用性事故。

Q2 有 quorum 就一定不会脑裂吗?

▸ 答案:不一定:quorum 只保证 “同一时刻只有一个多数派”,但如果旧主 “假死”(进程还在、网络分区),它可能仍持有写权限并继续写存储——这就要靠 fencing(存储侧拒绝旧 token)。quorum 防 “选主冲突”,fencing 防 “旧主写入”。

Q3 2 节点集群为什么需要仲裁者?

▸ 答案:因为 2 节点在分区时是 1:1,没有多数派,谁都不能写(或两边都写,取决于配置)。加一个轻量 witness(不存数据、只投票)后变成 3 节点,多数派为 2,可以在 “一主一从 + witness” 侧继续服务。

Q4 “假死” 场景下,光有 quorum 为什么不够?

▸ 答案:因为假死的节点可能仍能访问共享存储(网络分区只影响心跳,不影响存储路径)。它以为自己还是主,继续写;新主也在写 → 分叉。fencing(存储侧 token/租约)能让它的写失败,这才是根治。

Q5 少数派应该 “只读” 还是 “完全拒绝”?

▸ 答案:看业务:只读能保住查询可用性(但可能读到旧值);完全拒绝更安全但影响更大。金融类通常拒绝写、允许有限只读;内容类可以只读。无论哪种,都要在文档里写清 “分区期间的降级行为”。

Q6 如何发现已经发生脑裂?

▸ 答案:三个信号:双主告警(两个节点都报 “我是主”)、数据校验不一致(同一 key 两个版本/时间戳)、复制冲突(双向复制报冲突)。发现后第一步是止血(停掉一方写入),再谈合并。

Q7 脑裂恢复后怎么合并数据?

▸ 答案:按业务语义:有唯一键/版本号的(如订单号 + 单调版本)按版本取新;计数器类用 CRDT 合并(见 25.6 CRDT (计数 / 集合 / 寄存器));金额/库存必须人工对账,不能自动 “取一个”。自动合并只适用于可证明可交换的结构。

关联