跳到正文

Redis 双活与故障切换 ​

一句话:多数被称为「双活」的 Redis 部署,实际是「单写双读」或「主备 + 就近读」;它用异步复制的数据丢失窗口换机房级故障时的快速恢复,代价是路由、同步、回滚三件事都要提前设计,切一个 IP 并不等于完成切换。

适用版本:Redis 7.x / 8.x。复制机制见 06-主从复制,集群拓扑见 08-集群模式,指标口径见 15-Redis监控指标与告警。

为什么需要 ​

单机房故障不会因为 Redis 有主从而消失。主从和 Cluster 的选主都发生在同一个集群内,机房整体不可用时,副本和主节点一起失联,应用仍然没有可写的 Redis。于是需要第二个机房、第二套集群,以及一条把请求从 A 机房切到 B 机房的路径。真正难的不是「切过去」,而是四件事同时成立:应用不写死 IP,切换前知道会丢多少数据,切换后能验证读写正常,失败时能回到一个确定的状态。任何一件没做到,所谓双活就只是一次高风险的手工改配置。

核心机制 ​

先把「双活」定义清楚 ​

模式写入读取数据同步主要代价
主备单写只有 A 机房可写A 为主,B 待命异步复制或外部同步切换有 RPO,B 平时不承载写
单写双读只有 A 机房可写A/B 都可读,B 可能读到旧值异步复制读到旧数据,切机房时读路径也要切
双写多活A/B 都可写各自本地读双向同步或业务双写冲突合并、幂等、回滚都变成业务问题

先回答三个问题:谁写、怎么同步、冲突怎么办。异步复制的主备不是强一致多写,切换时丢最近写入是机制决定的。Redis Cluster 自身只解决一个集群内的分片和选主,不提供跨集群复制,也不提供双写冲突合并。如果两边都能写却没有冲突规则,那不是双活,是双写事故。平台通常只做「路由切换」:把逻辑集群指向另一套已经存在的集群,不负责把缺失的数据补过去;数据同步是独立链路,必须单独验证。

路由:应用认逻辑名,不认 IP ​

应用配置里保存的是逻辑集群名,例如 order-cache,由配置中心把它映射到当前主集群的地址列表。示例命名可用 LogicalCluster 表示这个映射,用 RedisClusterRegistry 管集群注册;关键是应用只依赖逻辑名,不依赖某个机房的 IP。

客户端启动时解析逻辑名并缓存地址,配置版本变化时刷新;刷新失败要有 TTL 兜底和重连策略。切换机房时,DNS/VIP 和 SDK 是两条不同的路径:

  • DNS:受 TTL 和客户端 DNS 缓存影响,旧连接可能不断、缓存可能不刷新,配置改了但请求还在旧机房。
  • VIP:漂移快,但连接池仍握着旧地址,漂移后新旧连接可能混用。
  • SDK + 配置中心:可控、能分批,但应用必须接入 SDK,配置推送本身可能形成重连风暴。

不要同时改 DNS 和 SDK 配置,先确认哪条是权威路径。否则一边已经切了、另一边还在用旧地址,排障时很难判断请求到底去了哪里。

切换前检查 ​

  • 源和目标都健康:cluster_state:ok,16384 个 slot 全覆盖,cluster_slots_fail:0。
  • 复制链路正常:master_link_status:up,副本 lag 在业务 RPO 以内,offset 差没有持续扩大。
  • 目标容量足够:内存余量能放下现有数据、副本缓冲和切换后的增长;不能等 maxmemory 顶满才切。
  • 应用清单完整:知道哪些应用连接、连接数多少、客户端是否支持配置刷新和重连。
  • 配置与版本有效:逻辑集群映射存在且版本正确,没有两边同时写的配置;主备版本、ACL、淘汰策略和持久化配置不能有关键差异。应急模式可以跳过变更窗口、通知这类软条件,但不能跳过目标健康、槽完整和配置有效;把「应急」理解成「全部检查都跳过」,通常会把一次机房故障扩大成数据事故。

切换流程 ​

真正决定成败的是冻结写入和确认追平,不是最后那次配置发布;源端还有在途写时,追平数字会骗人。配置发布后应用重连是第二个故障点,必须分批、加抖动、限流。观察窗口内不要提前释放旧集群,也不要把回滚点删掉。

演练与应急 ​

演练不改变路由:跑完整检查,对目标做只读探测,记录每项检查的耗时和失败项,产出报告。它证明的是检查项和预案可用,不证明真实切换一定成功;所以演练要覆盖客户端重连,而不只是平台接口返回成功。应急才做真实切换:可以缩短审批、跳过软检查,但仍要保留目标健康、槽完整、配置有效这三条硬条件,并保留操作留痕和回滚点。没有演练过的预案,在故障现场通常不可信。

数据一致性 ​

异步复制下,源集群确认写成功但尚未同步到目标的那部分数据,会在切换时丢失。WAIT numreplicas timeout 只能让当前这条写等待指定数量的副本确认,不能把后续写变成同步复制;min-replicas-to-write 默认 0、min-replicas-max-lag 默认 10(Redis 7.x),调大前者可以在副本不足时拒绝写入,用可用性换更小的丢失窗口。切换前要先冻结入口写入并排空在途请求,再等待复制追平;只在 Redis 侧切只读,应用侧仍在重试,追平数字就不可信。数据也要提前分类:

数据能否接受丢失处理方式
普通缓存可以回源重建,接受短时命中率下降
会话通常可以允许重新登录,或双写会话存储
分布式锁不可以需要 fencing token,不能靠双活保证互斥
队列/Stream不可以单独做同步复制或业务补偿,不能混在缓存切换里
计数/流水视业务幂等键、对账和补偿,不能假设 Redis 不丢

不能丢的数据,双活不是答案。要么用同步复制或 Quorum 把写入确认拉到两个机房,要么业务侧具备幂等和补偿能力;只在切换后说「应该没丢多少」,不是一致性方案。

回滚 ​

回滚条件要在切换前写死:目标集群不健康、错误率或 P99 超过阈值、读写探测失败、应用大面积重连失败。最大回滚时间也要提前定,超过就转前向修复,避免在现场反复切。在单写切换模式下,回滚窗口只到新主第一次接受写之前。此后切回原集群,需要处理已经写入新主的数据、双向写入的冲突和客户端缓存,本质是一次反向数据迁移,不是再点一次按钮。步骤是:冻结新主写入、确认增量、反向同步、切回配置、验证、对账;任何一步没有完成,都不要放开双写。

最小上手 ​

以下命令在 Redis 7.x 执行。SET 探测会写一个带 TTL 的专用 key,不要用业务 key 做探测。

bash
export REDISCLI_AUTH='<password>'
# 切换前:在源集群每个主节点上查看复制链路与副本延迟
redis-cli -h "$SRC_HOST" -p "$SRC_PORT" --no-auth-warning INFO replication |
grep -E 'role:|master_repl_offset:|slave[0-9]+:'

# 切换后:在目标集群验证槽覆盖和读写
redis-cli -h "$DST_HOST" -p "$DST_PORT" --no-auth-warning CLUSTER INFO |
grep -E 'cluster_state|cluster_slots_assigned|cluster_slots_fail'

redis-cli -c -h "$DST_HOST" -p "$DST_PORT" --no-auth-warning \
  SET '__switch_probe__' "$(date +%s)" EX 60
redis-cli -c -h "$DST_HOST" -p "$DST_PORT" --no-auth-warning GET '__switch_probe__'

预期输出类似:

text
role:master
master_repl_offset:918273645
slave0:ip=10.0.0.12,port=6379,state=online,offset=918273640,lag=0
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_fail:0
OK
1750000000

判断依据:副本 state=online、lag 在 RPO 内,offset 差没有持续扩大;offset 是字节数,要结合写入速率换算成时间,不能只看「差一点」。切换后 cluster_state:ok、槽覆盖 16384、cluster_slots_fail:0,SET 返回 OK 且 GET 读回同一个值;如果返回 READONLY You can't write against a read only replica,说明目标没有提升为主,或路由仍指向副本。

生产动作 ​

  • 权限分离:检查、执行、回滚是三个权限;生产切换需要双人复核,应急通道可以事后补审批,但不能没有留痕。
  • 审计固定字段:操作人、复核人、模式、源/目标集群、切换前后的配置版本、检查结果、回滚点、验证结果。
  • 切换后清单:配置版本已生效、应用连接数恢复、读写探测通过、P99 和错误率回到基线、旧集群无残留连接、后台任务没有重复消费。
  • 重连与看板:配置分批推送,客户端重连加随机抖动,连接池预热;盯 connected_clients、rejected_connections 和 CPU,避免所有应用在同一秒重连,并把源/目标 cluster_state、复制延迟、错误率、P99、连接数和配置版本分布放在同一屏。
  • 演练周期:按业务 RTO/RPO 要求定期演练,记录上次演练时间和失败项;没有固定周期的演练,很容易退化成一次性的文档检查。

边界 ​

  • 双活不是备份,也不能替代演练与回滚:误删、写坏和逻辑错误会同步到另一端,仍然需要独立备份和可验证的恢复流程;没有演练和回滚方案,不要做真实切换。
  • 跨地域延迟决定一致性上限:同步复制要把 RTT 加到写路径上;不能接受这个延迟,就只能异步复制并明确 RPO。
  • 托管服务与 DNS 都有差异:部分云 Redis 不开放 REPLICAOF、CLUSTER 管理命令或跨地域复制;DNS/VIP 的 TTL、客户端缓存和旧连接也可能让请求留在原机房,切换后必须从应用侧验证。
  • 双写需要 fencing:两边都可写时,靠人工约定「先写 A 再写 B」无法防止脑裂,必须有租约、版本号或仲裁。

讲清楚 ​

多数 Redis 双活其实是单写双读或主备加就近读,真正的多写需要业务自己解决冲突。它要解决的是机房整体不可用时还能写,所以应用必须认逻辑集群名,由配置中心切换指向,而不是写死 IP。切换前要检查两端健康、槽完整、复制延迟和目标容量;切换时先冻结写入、等复制追平、再提升目标、发布路由、让客户端重连并验证读写。异步复制一定有丢失窗口,不能丢的数据要靠同步复制或业务补偿。回滚只在新主接收写入前成立,之后切回是反向迁移,不是按钮。双活也不能替代备份。

相关:06-主从复制、07-哨兵模式、08-集群模式、15-Redis监控指标与告警、99-生产检查清单。

把一门技术讲透到既懂也会用