跳到主内容

Redis 哨兵脑裂复盘:min-replicas 守住数据不丢 ​

事故背景:哨兵切换后,客户端为什么还在写旧主库 ​

某次业务高峰期,机房之间出现瞬时网络抖动。Redis Sentinel 在多数派投票后,将一个从库提升为新主库;但旧主库所在分区并未立刻感知自己已被降级,部分旧连接仍继续向它写入。网络恢复后,旧主库以从库身份重新同步新主库,这段时间写入旧主库的数据被全量覆盖,最终表现为订单状态回退、用户重复扣款校验失败。

这不是 Sentinel “选错了主”,而是典型的分布式脑裂:同一时刻客户端看到两个可写 Master。Sentinel 能解决自动故障转移,却不能天然阻止旧主在分区期间继续接收写请求。真正兜底数据一致性的关键参数,是 min-replicas-to-write 与 min-replicas-lag。

脑裂是如何发生的:Sentinel 故障转移链路 ​

Sentinel 判定主库主观下线后,需要足够数量的 Sentinel 同意,才会进入客观下线并发起故障转移。问题在于,客户端与旧主之间的连接、旧主所在网络分区、Sentinel 多数派网络可能彼此独立。

客户端旧连接继续写旧主旧 Master少数分区仍可写Sentinel 多数派提升新主新 Master接收新流量网络恢复旧主变从并同步

事故中的直接现象有三个:第一,INFO replication 在旧主上短时间仍显示 role:master;第二,业务连接池没有订阅哨兵切换事件,仍复用旧 TCP 连接;第三,旧主没有配置最小从库数量,即便所有从库都已失联,它依旧返回写入成功。

方案对比:只靠客户端切换够不够 ​

常见治理方案有三类。

方案解决问题局限适用建议
客户端订阅 Sentinel 切换新流量尽快连到新主旧连接、网络分区内客户端无法被强制纠正必须做,但不是数据一致性兜底
调大 down-after-milliseconds降低抖动导致的误切换故障恢复变慢,真宕机时影响可用性按业务 RTO 谨慎调整
配置 min-replicas-to-write从库不足时禁止主库写入,阻断脑裂脏写牺牲少量写入可用性对不能接受丢失确认写入的核心链路必配

核心取舍是:当主库看不到足够健康从库时,是继续允许写入并承担覆盖丢失风险,还是快速失败让业务重试到新主。对订单、余额、库存等场景,后者通常更安全。

参数原理:min-replicas 如何让旧主主动拒绝写入 ​

Redis 主库会持续接收从库的 ACK,复制偏移量推进情况可在 INFO replication 中看到。配置以下参数后,如果健康从库数量低于阈值,主库会对写命令返回错误:

bash
# 至少有 1 个健康从库,主库才接受写命令
CONFIG SET min-replicas-to-write 1

# 从库延迟不能超过 10 秒;超过则不计入健康从库
CONFIG SET min-replicas-lag 10

# 持久化到配置文件,避免重启后丢失
CONFIG REWRITE

# 查看复制状态
INFO replication

正常一主二从环境中,返回片段类似:

bash
# Master 视角
role:master
connected_slaves:2
slave0:ip=10.0.0.11,port=6379,state=online,offset=1288,lag=0
slave1:ip=10.0.0.12,port=6379,state=online,offset=1288,lag=0

此时写入关键 Hash 结构会成功:

bash
# 订单状态 Hash:字段表示状态、版本号与更新时间
HSET order:10001 status PAID version 8 updated_at 1710000000
# 返回 (integer) 3

HGETALL order:10001
# 1) "status"
# 2) "PAID"
# 3) "version"
# 4) "8"
# 5) "updated_at"
# 6) "1710000000"

当网络分区导致两个从库都不可见时,connected_slaves 变为 0,旧主继续执行写命令会得到:

bash
HSET order:10001 status SHIPPED version 9
# 返回错误示例:
# NOREPLICAS Not enough good replicas to write.

这正是想要的保护效果:旧主不能再制造“已确认但不会进入新主数据集”的写入。应用层应捕获该错误,进行重试、熔断或转入失败队列,而不是把它误判成普通 Redis 超时。

可运行的 Sentinel 高可用验证步骤 ​

下面用三个 Redis 实例与三个 Sentinel 实例模拟一主二从。生产环境应分散到不同机器、机架或可用区,示例端口仅用于本地验证。

bash
# 启动三个 Redis 节点
redis-server --port 6379 --daemonize yes --dbfilename dump-6379.rdb
redis-server --port 6380 --daemonize yes --dbfilename dump-6380.rdb
redis-server --port 6381 --daemonize yes --dbfilename dump-6381.rdb

# 将 6380、6381 配置为 6379 的从库
redis-cli -p 6380 REPLICAOF 127.0.0.1 6379
redis-cli -p 6381 REPLICAOF 127.0.0.1 6379

# 在主库上开启脑裂写入保护
redis-cli -p 6379 CONFIG SET min-replicas-to-write 1
redis-cli -p 6379 CONFIG SET min-replicas-lag 10

创建三个 Sentinel 配置文件,内容如下:

bash
# sentinel-26379.conf
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
bash
# sentinel-26380.conf
port 26380
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
bash
# sentinel-26381.conf
port 26381
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

启动并观察:

bash
redis-server sentinel-26379.conf --sentinel --daemonize yes
redis-server sentinel-26380.conf --sentinel --daemonize yes
redis-server sentinel-26381.conf --sentinel --daemonize yes

# 查看 Sentinel 认定的主库地址
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

# 手动触发一次故障转移,验证客户端切换逻辑
redis-cli -p 26379 SENTINEL failover mymaster

验证时建议同时观察两个关键指标:

bash
# 谁是主库
redis-cli -p 6379 INFO replication | grep role
redis-cli -p 6380 INFO replication | grep role
redis-cli -p 6381 INFO replication | grep role

# 主库当前有多少在线从库
redis-cli -p 6379 INFO replication | grep connected_slaves

配置建议:阈值不是越大越好 ​

一主二从通常设置 min-replicas-to-write 1。它允许一个从库故障时继续提供写入,又能在主库完全失去从库连接时拒绝写入,兼顾可用性与安全性。如果设置为 2,那么任意一个从库短暂抖动都会让主库拒绝所有写请求,容易把高可用系统变成“频繁只读”。

min-replicas-lag 也要结合复制延迟与慢命令情况设置。过小会把正常抖动误判为不健康,过大则可能让延迟很高的从库仍被算作有效副本。普通在线业务可从 10 秒开始,结合 master_repl_offset 与从库 ACK 监控逐步收紧。

需要强调的是,该参数只能降低异步复制场景下脑裂期间的脏写窗口,并不能把 Redis Sentinel 变成强一致存储。客户端如果要求“写成功即绝对不丢”,还需要配合 WAIT、业务幂等、对账补偿,或使用更强一致的系统。

避坑总结:生产环境至少检查这 5 件事 ​

  1. 只配置 Sentinel quorum,却忘记配置 min-replicas:常见现象是切换成功后,旧主分区内仍能 SET 成功,网络恢复后这些键被覆盖。修正方法是在所有潜在主库上持久化 min-replicas-to-write 1 与合理的 min-replicas-lag,并通过配置管理确保新提升的从库也带有同样配置。

  2. 客户端缓存旧主地址或长期复用连接:日志中已经出现 switch-master 事件,但业务仍报写入旧主失败。必须使用支持 Sentinel 的客户端,订阅主从切换消息;连接池遇到 READONLY、连接拒绝、主库角色变化时要主动丢弃旧连接并重新发现主库。

  3. 把 down-after-milliseconds 设得过低:一次几秒钟的网络抖动就触发频繁选举,甚至出现来回故障转移。不要把它当成防脑裂的主要手段,应基于健康检查超时、跨机房 RTT 和业务可接受 RTO 设置,并先在预发布环境注入延迟、丢包和断连故障。

  4. 误认为 NOREPLICAS 是 Redis 故障而无限重试:分区期间疯狂重试旧主会放大线程池与连接池压力。应用层应把 NOREPLICAS Not enough good replicas to write. 识别为保护性拒绝,触发主库刷新、有限重试和熔断;关键写请求要带幂等键,避免恢复后重复执行。

  5. 只做手动 failover,不做真实分区演练:测试环境能切换不代表业务能正确处理双主窗口。建议用防火墙或流量控制模拟主库与 Sentinel、主库与从库、客户端与 Sentinel 三类隔离,并核对复制偏移量、写入返回值、切换后数据版本与业务对账结果。

最终复盘结论很简单:Sentinel 负责自动选主,min-replicas 负责在主库失去安全复制条件时停止写入,客户端负责及时换主与失败重试。三者同时到位,才能把脑裂造成的数据丢失窗口压缩到可控范围。

免责声明

本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。