如果光缆被挖断导致 Redis 出现两个 Master,怎么防止数据丢失?
当前时间:2026年4月23日,新加坡。
案发场景:
你们的支付系统使用了极其标准的 Redis 高可用架构:1 个 Master,2 个 Slave,外加 3 个 Sentinel(哨兵)。
Master 部署在机房 A,Slave 和哨兵部署在机房 B。客户端应用也部署在机房 A。
灾难降临(网络分区的死神):
晚上 10 点,机房 A 和机房 B 之间的主干光缆被挖掘机挖断了(发生了网络分区)。
精神分裂开始:
- 机房 B 的视角:3 个哨兵发现联系不上 Master 了,它们立刻举行紧急会议(Raft 选举),把机房 B 里的一个 Slave 提拔成了新的 Master。
- 机房 A 的视角:原本的老 Master活得好好的。并且,客户端应用和老 Master 处在同一个机房,网络是通的!
此时,你的宇宙里出现了两个 Master(脑裂)!
客户端浑然不知,继续疯狂地把大量的支付订单写入了机房 A 的老 Master。
惨剧的终章(破镜难重圆):
10 分钟后,光缆修好了。机房 A 和 B 重新连通。
哨兵系统巡视了一圈,发现了尴尬的一幕:怎么有两个 Master?
哨兵残酷地降级了机房 A 的老 Master,逼迫它变成新 Master 的 Slave。
大屠杀发生:按照 Redis 的主从同步铁律,老 Master 在变成 Slave 的那一刻,会立刻清空自己的所有内存,去全量复制新 Master 的数据!
在光缆断裂的这 10 分钟内,客户端写入老 Master 的几十万笔订单数据,瞬间灰飞烟灭,永远消失,连 AOF 日志都救不回来。
面对这种极其残忍的数据丢失,架构师们必须祭出极其克制的防御手段。
1. 核心原理解剖:为什么 Redis 会丢数据?
很多初级研发会问:“既然老 Master 接收了数据,网络恢复后,它把这 10 分钟的数据推送给新 Master 不就行了吗?”
这是关系型数据库(如 MySQL 双主架构)或者 Git 合并分支的思维。但Redis 绝对不支持多活写冲突合并!
Redis 的设计哲学是极度追求吞吐量:
- 异步复制机制:客户端向 Master 写入
SET order:10086 "paid",Master 瞬间把数据写进内存,不等待 Slave 确认,直接向客户端返回OK。 - 独裁覆写原则:在主从架构中,Master 是绝对的真理。Slave 连上 Master 的第一件事,就是无脑丢弃自己不同的数据,完全以 Master 的 RDB 快照为准。
正因如此,当脑裂恢复时,系统只能认准一个“真理”。而由于哨兵提拔了新 Master,老 Master 就成了叛军,它的数据必须被无条件清洗。
2. 终极防御:min-replicas的双刃剑
既然数据在恢复时注定会被清洗,那我们唯一的防守策略就是:在脑裂发生(网络断开)的期间,强行禁止客户端向老 Master 写入数据!
Redis 为此提供了两个极其硬核的保命配置(在 Redis 5.0 之前叫min-slaves-to-write,5.0 之后改名):
在老 Master 的redis.conf中配置:
# 要求 Master 至少有 1 个 Slave 保持正常连接 min-replicas-to-write 1 # Master 和 Slave 之间失去联系的延迟时间,不能超过 10 秒 min-replicas-max-lag 10防御工作流推演:
回到刚才光缆被挖断的 10 分钟:
- 光缆断裂,老 Master 发现自己的两个 Slave 都没有按时发送
ACK心跳。 - 过了
10秒钟,老 Master 触发了min-replicas-max-lag的底线。 - 此时,虽然客户端还在机房 A 疯狂向老 Master 发送
SET命令,但老 Master 直接拒绝服务!它会向客户端抛出一个冷酷的错误:NOREPLICAS Not enough good replicas to write. - 客户端写入失败,抛出异常(随后触发业务层的降级,比如把消息暂存到本地或发到 MQ)。
- 10 分钟后网络恢复,老 Master 被降级并清空。但由于它之前拒绝了所有的写入,所以根本没有丢失任何有效数据!
3. CAP 定理的无情制裁:保命的代价
你以为配上这两个参数就万事大吉了?天下没有免费的午餐。
当你开启min-replicas-to-write时,你实质上是在CAP 定理(一致性、可用性、分区容错性)中,放弃了可用性(Availability),选择了强一致性(Consistency)。
架构师的灾难推演(新的风暴):
假设你的架构是 1 主 1 从(为了省钱)。你配置了min-replicas-to-write 1。
某天,网络没有任何问题,仅仅是你的Slave 机器主板烧了,宕机了。
此时,你的 Master 活得好好的,但它发现自己唯一的一个 Slave 失去了联系(lag > 10秒)。
结果:Master 为了遵守配置规则,当场“自爆”,拒绝所有客户端的写请求!
仅仅死了一个从库,却导致整个系统的写入功能全部瘫痪!
实战权衡铁律:
- 如果你用 Redis纯做缓存:绝对不要开启这个参数!脑裂丢了缓存就丢了,大不了去 MySQL 里重新捞。可用性(活着)大于一切。
- 如果你用 Redis做分布式锁、或者发号器、核心库存扣减:强烈建议开启!宁可让用户在几分钟内无法下单报错,也绝对不能让同一把锁被两个人同时拿到,导致严重的资金对账灾难。
- 如果必须开启,请确保至少有 3 个数据节点(1 主 2 从),配置
min-replicas-to-write 1。这样即使死了一个从库,还有一个从库能保底,防止 Master 误杀自己。
4. 彻底解决脑裂的终极方案:Redlock 或外部协调组件
其实,无论怎么调整min-replicas,只要 Redis 本质上依然是异步复制的 AP(高可用)模型,它就无法从根本上解决网络分区带来的“脏读脏写”问题。
如果你的业务对数据一致性要求达到了**“金融级绝对正确”**的苛刻程度:
- 不要用 Redis Sentinel/Cluster 去扛强一致性场景!
- 引入基于Raft 或 Paxos 共识算法的分布式协调系统,如ZooKeeper或etcd。
- 在 ZK 中,写入数据必须得到**半数以上(Majority)**节点的确认。当发生网络分区(脑裂)时,被隔绝的老 Leader 节点由于无法收集到半数确认,天然无法执行任何写操作,从物理学上绝对杜绝了脑裂数据丢失的可能。
总结
Redis 脑裂,是一场由于“网络光缆”和“异步复制机制”共同上演的黑色幽默。
当我们享受 Redis 单机数十万并发写入的极速快感时,我们实际上是把“数据一致性”作为筹码,换取了这种极致的速度。min-replicas配置就像是给这辆狂奔的跑车装上了一个安全气囊:当系统发现自己驶入未知的浓雾(网络分区)时,强制踩死刹车,牺牲掉这段时间的可用性,换取车毁人亡前的最后生机。
