缓存主从复制延迟会带来什么问题?
简化版
异步主从复制会让从节点短时间落后,导致读到旧值、刚删除的 Key 在故障切换后“复活”,以及主故障时丢失尚未复制的写。它不能靠“通常延迟很低”忽略;要按业务选择读主、会话读己之写、版本校验、等待复制确认或接受明确的陈旧窗口,并监控字节/时间 lag。
详细版
Redis 复制通常是异步的:主节点执行写并返回后,从节点稍后接收复制流并应用。延迟来源包括网络、从节点 CPU/磁盘、Big Key、大批量命令、全量同步和输出缓冲。lag 会影响读写分离、缓存删除传播和故障切换的数据完整性。
常见问题包括用户写后从从库读到旧数据;删除尚未复制时主故障,新主仍有旧 Key;计数/限流读旧值;故障切换丢失未复制写。对一致性敏感的读应走主节点或携带最低版本;WAIT 可等待指定副本确认但不等同强一致事务,故障场景仍可能有边界。
监控复制 offset/字节差、秒级 lag、从库 link 状态、repl backlog、full sync、应用版本差和切换后的数据校验。发生 lag 时先限制大写与慢命令、保护主从网络、停止从库敏感读;恢复后确认追平再重新放量。
完整版教学
一、异步复制的确认边界
客户端向主节点写入,主节点执行后通常可以立即返回,不必等待从节点应用。随后主把命令流发送给从节点。这个设计写延迟低、可用性好,但“主已返回成功”不代表任何副本都已持久拥有该写。
client -> primary: SET k v2
primary -> client: OK
primary -- replication stream --> replica (稍后应用)
在复制窗口内读从节点可能得到 v1。若主节点此时永久故障并提升未收到 v2 的从节点,v2 可能丢失。
记忆钩子:异步复制的成功是“主节点已接受”,不是“新值已安全存在于所有副本”。
二、哪些业务症状来自复制延迟
最直观的是写后读旧:用户更新昵称后刷新又看到旧值。删除也可能倒退:主删掉权限缓存,从还保留旧值;故障切换后旧值重新可见。计数、限流或锁若从副本读取,旧状态可能破坏语义。
| 场景 | lag 结果 | 风险 |
|---|---|---|
| 普通详情 | 短暂旧内容 | 体验问题 |
| 权限撤销 | 从库仍返回允许 | 安全问题 |
| 缓存删除 | 切换后旧 Key 复现 | 长时间脏读 |
| 计数器 | 读到较小值 | 限流/配额失真 |
| 主故障提升 | 未复制写丢失 | 数据与缓存状态倒退 |
缓存不是事实源时可回源修复,但错误值仍可能影响业务决策,不能一概视为无害。
三、延迟从哪里产生
主节点产生复制流的速度超过从节点消费速度就会积压。原因包括从节点 CPU 饱和、网络抖动、大 Key 传输、执行耗时长的命令、AOF/RDB 干扰,以及断线后的全量同步。主节点输出缓冲也可能因从节点长期跟不上而膨胀并断开连接。
假设主每秒产生 80MB 复制数据,从只能处理 60MB,每秒积压 20MB;5 分钟达到 6GB。若 backlog 只有 1GB,断线从节点恢复时可能无法部分重同步,只能全量同步,进一步增加压力。
lag_growth = primary_replication_rate - replica_apply_rate
若持续 > 0,延迟不会自行收敛
四、如何提供读己之写
最简单是写后一定时间读主节点,或同一会话粘到主;更精确的做法是写返回版本/offset,读请求要求副本至少达到该位置,否则等待、换主或返回重试。Redis 客户端通常不会自动为业务提供这种跨请求版本协议,需要应用设计。
对低风险数据可接受最大 2 秒旧值并展示时间;对权限、锁等高风险数据不要读异步副本。读写分离不是无条件优化,应按数据类型选择。
如果所有读最终都因 lag 回主,主节点要有容量,且切换策略不能造成回主风暴。
五、WAIT 能解决到什么程度
Redis WAIT numreplicas timeout 可在当前连接之前的写传播到指定数量副本后返回已确认副本数。它能缩小数据丢失窗口,但不是把 Redis 变成线性一致数据库:复制仍是异步,命令与 WAIT 之间存在应用使用方式,故障切换与持久化配置也影响结果。
SET order:42 paid
WAIT 1 100 # 最多等100ms,希望至少1个副本确认接收
返回0 -> 应用必须决定失败、重试还是接受风险
等待副本会增加写延迟并降低分区时可用性。只有确实需要更小 RPO 的少量写才适合,不能默认所有缓存写都同步等待。
六、故障切换时怎样控制风险
哨兵/Cluster 选新主时会考虑可用副本和复制偏移,但在异步模型下仍不能保证零丢失。可以配置主节点在可用副本数量不足或 lag 超阈值时拒绝写,换取更强的数据安全;代价是副本故障会降低写可用性。
切换后不要立即让一致性敏感流量全量读取。先核对新主 offset、关键版本和业务抽样;旧主恢复后应作为从节点全量/增量追赶,不能未经重同步重新对外。
对于可从数据库重建的缓存,切换后发现版本不确定可定向失效并限速回填,比保留可疑旧值安全。
七、监控与演练
监控每个从节点的 link 状态、offset 差、秒级 lag、复制吞吐、backlog 使用、full/partial sync 次数、输出缓冲和主从资源差异。应用侧还要记录从库读取的 value version 与期望最低版本。
演练网络限速、从节点 CPU 饱和、Big Key 写入和主故障,验证敏感读是否切主、写入策略是否按副本健康执行,以及切换后缓存旧值如何清理。恢复判断以 lag 归零并持续稳定为准。
replica_link: up
offset_bytes_behind: 1048576
lag_seconds: 0.08
full_sync_count_1h: 0
sensitive_reads_from_replica: 0
八、常见误区与追问
- 误区:Redis 很快,所以主从复制没有延迟。 任何异步复制都有窗口,网络、Big Key 与从库负载会放大。
- 误区:缓存数据可重建,所以复制丢失无影响。 旧权限、计数和删除复活可能在重建前影响业务。
- 误区:使用
WAIT就获得强一致。 它只是等待一定副本确认,不能覆盖所有故障与持久化边界。 - 误区:平均 lag 很低即可。 应看最大/P99、最落后副本和敏感读的版本要求。
- 追问:如何保证用户写后立刻读到? 写后读主、携带最低版本/offset,或在副本追平前粘到主。
- 追问:lag 持续增长怎么办? 比较产生与应用速率,限写/大命令、修复网络和从库资源,必要时重建副本。
- 追问:故障切换能否零丢失? 异步复制一般不能绝对保证;可等待副本/限制写换取更小窗口,但牺牲延迟与可用性。
九、加强记忆
把复制延迟记成“主已成功,不等于从已看见”。它会造成读旧、删除复活和切换丢写;普通数据可接受明确陈旧窗口,高风险数据读主或校验最低版本。WAIT 与最小副本策略只能缩小风险而非消灭异步边界,最终要用 lag、offset 和故障演练证明策略有效。