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 (计数 / 集合 / 寄存器));金额/库存必须人工对账,不能自动 “取一个”。自动合并只适用于可证明可交换的结构。