Redis - 主从同步与故障切换的常见坑

Redis - 主从同步与故障切换的常见坑

文章目录

  • 主从数据不一致
    • 写后立即读
    • 主从延迟过大
  • 过期 key 的特殊处理
    • 3.2 之前的坑
    • EXPIREAT 的坑
  • 主从切换时的数据丢失
  • 全量同步的连锁反应
  • slave-serve-stale-data 配置
  • 主从架构的客户端配置
  • 主从同步的常见误区
    • 误区 1:从库可以分担写压力
    • 误区 2:增加从库提升性能
    • 误区 3:主从就是高可用
    • 误区 4:哨兵切换是无缝的
  • 实践建议RF

主从同步看起来是 Redis 高可用的基石——主库写、从库读、主库挂了切从库。流程简洁,但生产环境真正用起来,会发现细节里藏着不少坑。这些坑往往不是配置错误,而是机制本身的边界条件,平时跑得好好的,故障来了就暴露。

主从数据不一致

主从同步是异步的:主库写完就返回,再把命令发给从库。从库执行需要时间,所以同一个 key 在主从上的值在某些瞬间是不一样的。这种短暂的不一致大多数业务能容忍,但有几个场景会被它坑到:

写后立即读

应用刚向主库写完数据,下一个请求又被路由到从库读取,可能读到旧值。

解决方案:

  • 关键路径强制读主库,比如订单刚创建立刻读详情。
  • 写后短暂时间内本地缓存:客户端在写之后短时间内不查 Redis,直接用本地。
  • 基于业务接受度调整:很多场景几百毫秒的不一致根本不影响。

主从延迟过大

网络抖动、从库负载高、复制带宽不够,都可能让从库远远落后于主库。可以通过以下指标监控:

# 主库执行INFO replication# 看 slave_repl_offset 和 master_repl_offset 的差距

差距持续增大说明从库跟不上,要排查从库压力或网络。

过期 key 的特殊处理

Redis 的过期机制有两种:定期扫描和惰性删除。这两个都在主库进行。从库不会主动删过期 key,而是等主库下发DEL命令。

3.2 之前的坑

3.2 之前,从库收到读请求时会返回已经过期但还没被删的 key。这经常导致从库读到"逻辑上已经不存在"的数据。

3.2 之后修复了:从库在返回 key 之前会检查过期时间,过期了直接返回空。

EXPIREAT 的坑

如果用EXPIREAT设置绝对过期时间,主从机器的系统时间不一致,就会导致两边过期时间不同。建议:

  • EXPIRE(相对时间)代替EXPIREAT
  • 主从机器开 NTP 时钟同步。

主从切换时的数据丢失

哨兵切换主库时有个微妙的时间窗口:

1. 主库 X 发生网络故障 2. 哨兵判定 X 客观下线 3. 选出新主库 Y,让 Y 升级 4. 通知应用,写流量打到 Y 5. X 网络恢复

在第 1-3 步之间,X 还在处理客户端的写请求(如果只是哨兵和 X 的网络断了,客户端和 X 是通的)。这些写入没同步到 Y,等 X 恢复后被降为从库,和 Y 做全量同步,把这段时间的写全部丢掉。这就是脑裂。

防御方式(详见专门讲脑裂的章节):

min-slaves-to-write 1 min-slaves-max-lag 10

主库必须至少有 1 个从库且复制延迟不超过 10s 才接受写入,否则拒绝。这相当于在 CAP 里牺牲 A 换 C。

全量同步的连锁反应

从库重连主库时,如果断连时间长、repl_backlog_buffer不够大,就会触发全量同步。全量同步对主库影响很大:

  1. 主库 fork 出子进程生成 RDB(fork 阻塞主线程几十~几百毫秒)。
  2. RDB 文件可能几 GB 甚至几十 GB,传输占用网络带宽。
  3. 同步期间主库的写命令累积在replication buffer里,占用内存。
  4. 从库收到 RDB 后清空内存重新加载,期间不可用。

如果同时有多个从库一起请求全量同步,主库压力会被放大。所以:

  • repl_backlog_size调大,给增量同步留足缓冲。生产环境建议至少几百 MB,根据写入速率估算。
  • 避免主库挂多个一级从库,可以让二级从库挂在一级从库下面。
  • 错峰重启从库,避免雪崩式全量同步。

slave-serve-stale-data 配置

主从断连时,从库默认行为由这个配置决定:

slave-serve-stale-data yes

yes(默认):断连时从库继续返回旧数据,可能严重不一致。
no:断连时所有读请求返回错误,强一致但可用性下降。

根据业务对一致性的要求选择。强一致场景一定要设no

主从架构的客户端配置

客户端连接 Redis 时不要写死 IP。生产环境标准做法:

  • 通过哨兵或 VIP 获取当前主库地址。
  • 客户端库支持哨兵模式(如 Lettuce、Jedis)。
  • 切换发生时,客户端库自动重连新主库。

如果客户端写死了旧主库 IP,切换后写入会失败甚至成功(写到了已经降级为从库的旧主库,从库默认slave-read-only拒绝写入,但万一被改了配置就有数据丢失)。

主从同步的常见误区

误区 1:从库可以分担写压力

从库默认只读。即使开放写入,从库的写不会同步到主库或其他从库,只会自己保存,导致数据不一致。

误区 2:增加从库提升性能

从库读能分担主库读压力,但每个从库都要消耗主库的复制带宽。从库太多反而拖慢主库。一般 1-2 个从库够用。

误区 3:主从就是高可用

主从是数据冗余,不是自动高可用。没有哨兵就没有自动切换,主库挂了还是要人工干预。

误区 4:哨兵切换是无缝的

切换过程通常需要 10-30 秒,期间写入不可用。业务必须有重试和降级。

实践建议RF

  1. 必须配置哨兵,3 个或 5 个哨兵分布在不同机器。
  2. repl_backlog_size调大,避免频繁全量同步。
  3. 设置min-slaves-to-write防脑裂,根据业务对可用性的要求权衡。
  4. 监控复制延迟master_link_status和 offset 差距是关键指标。
  5. 客户端用哨兵模式,不要写死 IP。
  6. 从库个数适度,1-2 个就够,太多反而加重主库负担。
  7. 关注全量同步频率,频繁全量同步说明配置或架构有问题。

主从架构是 Redis 高可用的基础,但远不是全部。理解这些坑点,才能在故障发生时知道哪些是机制限制(必须接受)、哪些是配置问题(可以修复)。把不一致、过期、切换、全量同步这几个核心场景吃透,主从架构才能真正撑住业务。