脑裂数据丢失路径
脑裂导致数据丢失的完整过程
脑裂导致的数据丢失不是瞬间发生的,而是一个逐步演进的过程。理解这个路径有助于针对性地设计防护措施。
第一阶段:网络分裂
正常状态:
主节点 A ←→ 从节点 A1、A2 ←→ 其他主节点 B、C
所有节点通过网络正常通信。
脑裂触发:
主节点 A 与其他节点(包括它的从节点 A1、A2)之间的网络中断。
但 A 与客户端的连接可能仍然正常(取决于网络拓扑)。
此时,从 A 的视角看:从节点和其他主节点都不可达了。但从客户端视角看:A 仍然是可用的。
第二阶段:数据分岔(Divergence)
网络中断后,集群中的节点开始数据分岔:
旧主节点 A 侧
- 客户端仍然通过 A 的 IP 连接 A
- 客户端写入的数据都保存在 A 上(但无法同步到任何从节点)
- A 的 replication offset 无法增长
- A 的 repl_backlog 只记录本地变更
新主节点 A1 侧
- 其他主节点通过 Gossip 发现 A 不可达,标记为 PFAIL → FAIL
- 如果有半数以上主节点同意,触发从节点 A1 晋升
- A1 成为新主节点,开始接受客户端写入
- 从节点 A2 切换到 A1 的从节点
数据变化
A 上写入的 Key,在 A1 上不存在
A1 上写入的 Key,在 A 上不存在
第三阶段:网络恢复,发现冲突
当网络恢复后,A 重新能够与集群中的其他节点通信:
握手过程
- A 发送 PING 给其他节点
- 其他节点回复 PONG,告知 A 当前的集群状态
- A 发现集群中已经存在一个”主节点 A1″
- A 发现自己的 config epoch 比 A1 小(A1 晋升时 epoch 会递增)
角色宣布
Redis Cluster 的冲突解决规则非常简单:epoch 大的节点拥有更高的权威。A1 的 epoch 大于 A,因此:
- A 被集群告知它不再是主节点
- 集群强制将 A 降级为从节点
- A 开始执行
SLAVEOF A1,复制 A1 的数据
第四阶段:数据丢失
这是最关键的一步。当 A 被降级为从节点后:
清空自身数据
从节点在开始全量复制之前,会先执行 FLUSHALL(清空所有数据):
A 上的数据:
├── 与 A1 一致的数据(网络中断前同步的)
├── 脑裂期间客户端写入 A 的数据 ← 这一部分将要丢失
└── A 的执行日志(包含脑裂期间的写入操作)
全量同步
- A 向 A1 发送
PSYNC请求 - A1 生成 RDB 快照发送给 A
- A 加载 RDB,覆盖所有本地数据
结果:A 上的所有数据被 A1 的数据覆盖,脑裂期间在 A 上写入的 Key 全部丢失。
丢失数据的量化分析
最大丢失量
最大丢失数据量 = 脑裂持续时间 × 主节点写入 QPS × 平均 Key 大小
脑裂持续时间的上界 ≈ cluster-node-timeout(默认 15 秒)
例如:
– QPS = 10000 写入/秒
– 脑裂持续 ≈ 15 秒
– 平均每个 Key 100 字节
– 最大可能丢失 ≈ 15 × 10000 × 100 ≈ 15 MB
实际丢失量
实际丢失量通常小于理论值,因为:
1. 故障发现和选举需要时间(不是瞬间提升从节点)
2. 旧主节点在检测到与半数主节点失联后,可能主动停止写入(取决于配置)
3. 客户端可能同时感知到故障并切换连接
四种无法挽回的数据丢失场景
- 异步复制延迟丢失:主节点写入成功但尚未同步到从节点时宕机
- 脑裂写入丢失:脑裂期间写入旧主节点的数据在恢复后被覆盖
- 选举期间写入丢失:主节点宕机到从节点晋升期间,客户端写入失败的数据
- 全量同步覆盖丢失:旧主节点被降级为从节点时的全量同步
总结
脑裂数据丢失的路径可以概括为:网络分区 → 数据分岔 → 角色降级 → 全量同步覆盖。关键在于第三步”角色降级”后,旧主节点以 FLUSHALL + 全量同步的方式清除了脑裂期间写入的所有数据。理解这条路径后,就能明白为什么 min-replicas-to-write 和 min-replicas-max-lag 能在网络分裂前就阻断写入路径,从而从源头防止数据丢失。


暂无评论内容