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不够大,就会触发全量同步。全量同步对主库影响很大:
- 主库 fork 出子进程生成 RDB(fork 阻塞主线程几十~几百毫秒)。
- RDB 文件可能几 GB 甚至几十 GB,传输占用网络带宽。
- 同步期间主库的写命令累积在
replication buffer里,占用内存。 - 从库收到 RDB 后清空内存重新加载,期间不可用。
如果同时有多个从库一起请求全量同步,主库压力会被放大。所以:
repl_backlog_size调大,给增量同步留足缓冲。生产环境建议至少几百 MB,根据写入速率估算。- 避免主库挂多个一级从库,可以让二级从库挂在一级从库下面。
- 错峰重启从库,避免雪崩式全量同步。
slave-serve-stale-data 配置
主从断连时,从库默认行为由这个配置决定:
slave-serve-stale-data yesyes(默认):断连时从库继续返回旧数据,可能严重不一致。no:断连时所有读请求返回错误,强一致但可用性下降。
根据业务对一致性的要求选择。强一致场景一定要设no。
主从架构的客户端配置
客户端连接 Redis 时不要写死 IP。生产环境标准做法:
- 通过哨兵或 VIP 获取当前主库地址。
- 客户端库支持哨兵模式(如 Lettuce、Jedis)。
- 切换发生时,客户端库自动重连新主库。
如果客户端写死了旧主库 IP,切换后写入会失败甚至成功(写到了已经降级为从库的旧主库,从库默认slave-read-only拒绝写入,但万一被改了配置就有数据丢失)。
主从同步的常见误区
误区 1:从库可以分担写压力
从库默认只读。即使开放写入,从库的写不会同步到主库或其他从库,只会自己保存,导致数据不一致。
误区 2:增加从库提升性能
从库读能分担主库读压力,但每个从库都要消耗主库的复制带宽。从库太多反而拖慢主库。一般 1-2 个从库够用。
误区 3:主从就是高可用
主从是数据冗余,不是自动高可用。没有哨兵就没有自动切换,主库挂了还是要人工干预。
误区 4:哨兵切换是无缝的
切换过程通常需要 10-30 秒,期间写入不可用。业务必须有重试和降级。
实践建议RF
- 必须配置哨兵,3 个或 5 个哨兵分布在不同机器。
repl_backlog_size调大,避免频繁全量同步。- 设置
min-slaves-to-write防脑裂,根据业务对可用性的要求权衡。 - 监控复制延迟,
master_link_status和 offset 差距是关键指标。 - 客户端用哨兵模式,不要写死 IP。
- 从库个数适度,1-2 个就够,太多反而加重主库负担。
- 关注全量同步频率,频繁全量同步说明配置或架构有问题。
主从架构是 Redis 高可用的基础,但远不是全部。理解这些坑点,才能在故障发生时知道哪些是机制限制(必须接受)、哪些是配置问题(可以修复)。把不一致、过期、切换、全量同步这几个核心场景吃透,主从架构才能真正撑住业务。
