9.6 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-05-27 10:00 |
脑裂问题
概述
脑裂(Split Brain)是分布式系统中最危险的故障之一:集群因为网络分区被割裂成多个独立的子集群,每个子集群都以为自己是"正统",各自选举出 Leader,同时对外提供服务。如果不能妥善处理,就会出现两个 Master 同时接受写入,导致数据丢失或不一致。
一、什么是脑裂?
用一个简单的场景来说明:
flowchart TD
subgraph 原始集群
M["Master<br/>slot 0~5460"]
R1["Replica 1"]
R2["Replica 2"]
end
M -->|"正常复制"| R1
M -->|"正常复制"| R2
style M fill:#e8eaf6,stroke:#3f51b5
网络分区发生后:
flowchart TD
subgraph 分区A["网络分区 A (少数派)"]
M2["原 Master<br/>仍认为自己是 Master"]
end
subgraph 分区B["网络分区 B (多数派)"]
R1B["Replica 1<br/>被选举为新 Master"]
R2B["Replica 2"]
end
C1["部分客户端"] --> M2
C2["部分客户端"] --> R1B
style M2 fill:#fff3e0,stroke:#ff9800
style R1B fill:#e8f5e9,stroke:#4caf50
[!DANGER] 脑裂的后果
- 两个 Master 同时接受写入——客户端 A 写入原 Master,客户端 B 写入新 Master
- 网络恢复后,原 Master 降级为 Replica——它在分区期间接受的写入数据全部丢失
- 因为降级后要从新 Master 同步数据,覆盖掉分区期间的增量写入
[!QUESTION] 思考 在上面的场景中,原 Master 在分区 A 中仍然是"合法"的 Master(它不知道自己已被替换)。那么问题来了——谁来告诉它"你已经不是 Master 了"? 如果没有人告诉它,它就会一直接受写入,这就是脑裂的本质。
二、Redis Cluster 的脑裂防护
Redis Cluster 提供了两层防护机制,我们先看没有任何防护时会发生什么,再逐层叠加:
场景对比:有无防护的差异
| 防护等级 | 分区期间少数派 Master | 分区恢复后 | 数据丢失? |
|---|---|---|---|
| 无防护 | 继续接受写入 | 降级为 Replica,增量数据被全量同步覆盖 | ✅ 丢失 |
| 仅 epoch 冲突解决 | 继续接受写入 | 同上(epoch 只解决"谁是 Master",不防丢数据) | ✅ 丢失 |
| epoch + min-replicas-to-write | 主动拒绝写入 | 无增量数据需要丢弃 | ❌ 不丢失 |
防护层 1:Epoch 版本号冲突解决
Redis 使用 epoch(集群纪元) 来解决配置冲突。当网络分区愈合后:
flowchart LR
subgraph Before["分区愈合前"]
M_Old["Master<br/>epoch=5"]
M_New["新 Master<br/>epoch=6"]
end
After["epoch=6 胜出<br/>原 Master 降级为 Replica"]
Before --> After
style M_Old fill:#fff3e0,stroke:#ff9800
style M_New fill:#e8f5e9,stroke:#4caf50
- epoch 更大的配置被视为"更新的真相"
- epoch 较小的 Master 被强制降级为 Replica
- 这解决了"谁是 Master"的分歧,但不解决"数据丢失"的问题
防护层 2:写入拒绝(min-replicas-to-write)
这是防止数据丢失的关键配置。在 redis.conf 中设置:
# 至少需要 1 个在线的 Replica 才允许写入
min-replicas-to-write 1
# Replica 的最大延迟(秒),超过则不算"在线"
min-replicas-max-lag 10
工作原理:
flowchart TD
W["客户端写入请求"] --> C{"在线 Replica 数<br/>>= min-replicas-to-write?"}
C -->|是| A["接受写入"]
C -->|否| R["拒绝写入<br/>返回错误"]
style A fill:#e8f5e9,stroke:#4caf50
style R fill:#fce4ec,stroke:#e91e63
当脑裂发生时,被隔离的少数派 Master 发现自己无法联系到足够的 Replica,于是主动拒绝写入——虽然损失了可用性,但保证了数据不会在分区恢复后丢失。
[!TIP] 贝叶斯视角
min-replicas-to-write 1的含义是:"如果连一个 Replica 都联系不上,那我很可能已经不是集群的多数派了,还是别写了吧。"这是一种基于概率的保守策略。
[!WARNING] 这个配置的代价
min-replicas-to-write不仅仅在脑裂时生效——任何原因导致 Replica 不够数时,Master 都会拒绝写入,包括:
- Replica 短暂网络抖动或重启
- Replica 复制延迟超过
min-replicas-max-lag- 只部署了 1 个 Replica 且恰好宕机
这意味着配置了这个参数后,可用性会降低。生产环境中需要在一致性和可用性之间做出明确的取舍。
三、分区恢复后的处理流程
当网络分区愈合后,Redis Cluster 会自动完成"善后"工作。整个过程无需人工干预:
sequenceDiagram
participant Old as "原 Master (分区 A)"
participant New as "新 Master (分区 B)"
participant R2 as "Replica 2"
Note over Old,New: 网络分区愈合,节点重新连通
Old->>New: Gossip 交换状态
New->>Old: PONG (epoch=6)
Note over Old: 比较 epoch: 5 < 6
Old->>Old: 降级为 Replica
Old->>New: 全量/增量同步数据
Note over Old: 分区期间的写入被覆盖
具体步骤:
- Gossip 状态交换:分区愈合后,节点之间通过 PING/PONG 交换集群状态
- Epoch 比较:原 Master 发现新 Master 的 epoch 更大(6 > 5)
- 自动降级:原 Master 将自己降级为 Replica,停止接受写入
- 数据同步:从新 Master 进行全量(
FULLRESYNC)或增量(PSYNC)同步 - 数据覆盖:分区期间原 Master 接受的写入被新 Master 的数据覆盖,永久丢失
[!QUESTION] 思考 如果在分区期间,原 Master 上写入了 key A=1,新 Master 上也写入了 key A=2。分区恢复后,key A 的值是什么?为什么 Redis 选择这种"简单粗暴"的覆盖策略,而不是尝试合并两边的写入?
四、CAP 角度理解脑裂
脑裂问题本质上是 CAP 定理在网络分区(P)发生时的体现——你必须在一致性(C)和可用性(A)之间做出选择:
| 选择 | 牺牲 | 脑裂时的行为 | Redis 配置 |
|---|---|---|---|
| CP(一致性优先) | 可用性 | 少数派 Master 拒绝写入 | min-replicas-to-write 1 |
| AP(可用性优先) | 一致性 | 两边都接受写入,事后丢弃少数派数据 | min-replicas-to-write 0(默认) |
[!IMPORTANT] 两个易混淆的配置
min-replicas-to-write:控制脑裂场景下的 CP/AP 行为。设为 1 时,少数派 Master 主动拒绝写入(CP);默认为 0,不拒绝(AP)。cluster-require-full-coverage:控制slot 无主场景下的行为。设为yes(默认)时,任何一个 slot 没有 Master 则整个集群拒绝服务;设为no时,只有访问无主 slot 的请求失败,其余正常。两者解决的是不同的故障场景,不要混淆。
[!NOTE] Redis 不是强一致系统 即使配置了
min-replicas-to-write 1,Redis Cluster 的复制仍然是异步的。Master 写入成功后不会等待 Replica 确认,所以在 Master 宕机的瞬间,最后几条写入可能还没同步到 Replica——这是 Redis 为了性能做出的 trade-off。如果业务需要强一致(如金融交易),应该使用支持同步复制的系统(如 etcd、ZooKeeper),而非 Redis。
五、Sentinel 模式下的脑裂
上面讨论的都是 Redis Cluster 模式。但 Sentinel(哨兵)模式同样存在脑裂问题,防护思路类似但机制不同:
| 对比项 | Redis Cluster | Sentinel |
|---|---|---|
| 故障检测 | Gossip + PFAIL/FAIL 多数确认 | Sentinel 节点投票(quorum) |
| 选举机制 | Replica 向 Master 投票 | Sentinel 向其他 Sentinel 投票 |
| 脑裂防护 | min-replicas-to-write |
min-replicas-to-write(同样生效) |
| 冲突解决 | Epoch(集群纪元) | Epoch(配置纪元,由 Sentinel 递增) |
[!NOTE] Sentinel 的额外防护 Sentinel 模式中,需要多数 Sentinel 节点同意才会执行故障转移(
quorum参数)。如果 Sentinel 本身也被分区隔离,少数派的 Sentinel 无法凑够 quorum,也就无法选出新 Master——这本身就是一种脑裂防护。但原 Master 仍然可能在分区期间接受写入,所以
min-replicas-to-write在 Sentinel 模式下同样推荐配置。
六、生产环境建议
| 配置 | 推荐值 | 说明 |
|---|---|---|
min-replicas-to-write |
1 | 至少 1 个 Replica 在线才写入(脑裂防护的核心) |
min-replicas-max-lag |
10 | Replica 延迟超过 10 秒视为不在线 |
cluster-require-full-coverage |
按业务选择 | yes:slot 无主时集群整体拒绝服务;no:仅无主 slot 不可用 |
cluster-node-timeout |
15000 | 故障检测超时,太短易误判,太长切换慢 |
| Master 数量 | 奇数 | 3/5/7,避免偶数导致投票平局 |
[!IMPORTANT] 脑裂不是 Redis 特有的问题 任何分布式系统在网络分区时都可能面临脑裂:ZooKeeper(ZAB 协议)、Kafka(ISR + epoch)、etcd(Raft 共识)等都有各自的防护机制。理解脑裂的本质有助于在任何分布式场景中做出正确的架构决策。
关联笔记
- hhs/Redis/07-集群方案 — Redis Cluster 集群方案
- hhs/Redis/07-集群方案/Raft共识算法 — 故障转移的投票选举机制
- hhs/Redis/07-集群方案/Gossip协议 — 节点状态传播与分区检测
- hhs/Redis/06-主从与哨兵 — Sentinel 模式下同样存在脑裂问题