Files
cs-note/hhs/Redis/07-集群方案/脑裂问题.md
T
2026-05-27 11:24:47 +08:00

9.6 KiB
Raw Blame History

tags, create time
tags create time
分布式
脑裂
网络分区
CAP
故障转移
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] 脑裂的后果

  1. 两个 Master 同时接受写入——客户端 A 写入原 Master,客户端 B 写入新 Master
  2. 网络恢复后,原 Master 降级为 Replica——它在分区期间接受的写入数据全部丢失
  3. 因为降级后要从新 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: 分区期间的写入被覆盖

具体步骤:

  1. Gossip 状态交换:分区愈合后,节点之间通过 PING/PONG 交换集群状态
  2. Epoch 比较:原 Master 发现新 Master 的 epoch 更大(6 > 5)
  3. 自动降级:原 Master 将自己降级为 Replica,停止接受写入
  4. 数据同步:从新 Master 进行全量(FULLRESYNC)或增量(PSYNC)同步
  5. 数据覆盖:分区期间原 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 共识)等都有各自的防护机制。理解脑裂的本质有助于在任何分布式场景中做出正确的架构决策。

关联笔记