找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5033

积分

0

好友

705

主题
发表于 3 天前 | 查看: 22| 回复: 0

你应该见过这样的场景:凌晨三点,值班手机响了,监控大屏上一片红色告警。Redis 主节点挂了,从节点还活着但没有自动提升,业务方的请求开始大面积超时。值班工程师揉着眼睛打开 VPN,登录跳板机,手动执行故障转移脚本。从接到告警到服务恢复,过去了将近 8 分钟。

8 分钟,对于一个日常百万 QPS 的系统来说,大约是 4.8 亿次请求受到影响。如果这是千万 QPS 的系统呢?48 亿次。

这还不是最坏的情况。更可怕的是值班工程师在半梦半醒中敲错了一条命令,把本应提升为主节点的从节点给重启了,故障恢复时间直接翻倍。

手动故障转移的最大风险不在于速度慢,而在于它依赖人的判断和操作,而人在高压和疲劳下犯错的概率远超预期。

这就是为什么缓存系统的故障转移必须从手动走向自动。

故障转移的核心问题

在深入讨论自动化之前,先梳理一下故障转移到底要解决哪些问题。表面上看,故障转移就是“主挂了换一个”,但实际上它包含了一系列环环相扣的决策。

故障检测:如何判断节点真的挂了

这是故障转移的第一步,也是最容易出错的一步。

一个节点不响应了,它真的挂了吗?不一定。可能是网络抖动导致的短暂不可达,可能是节点正在执行一个耗时较长的操作(比如 Redis 的 BGSAVE),也可能只是监控链路本身出了问题。

如果把“不响应”直接等同于“故障”,就会频繁触发不必要的切换。每次切换都有成本:数据可能丢失,连接需要重建,缓存命中率会短暂下降。

故障检测的本质挑战是:如何在“发现得足够快”和“判断得足够准”之间找到平衡。

检测太慢,故障影响面扩大;检测太灵敏,误切换带来的副作用可能比故障本身还大。

主节点选举:谁来接班

确认主节点确实故障后,下一个问题是从多个从节点中选择谁来做新的主节点。这个选择并不是随机的,需要考虑几个因素:

  • 数据完整性:哪个从节点的数据最新,复制偏移量最大
  • 节点健康状况:候选节点本身是否健康,网络延迟是否正常
  • 优先级配置:运维是否有预设的优先级偏好
  • 网络拓扑:候选节点是否与大多数客户端在同一机房

客户端切换:如何让客户端知道新主在哪里

选出新主节点后,还需要通知所有客户端更新连接。如果客户端仍然向旧的主节点发送请求,故障转移就没有真正完成。

客户端切换的方式决定了整个故障转移的端到端耗时。常见的切换方式包括 DNS 更新、VIP 漂移、客户端自发现等,每种方式的切换速度和可靠性各不相同。

手动故障转移:原始但可控

最早的缓存故障转移完全依赖人工操作。运维工程师通过监控发现故障,手动执行一系列命令完成切换。

手动故障转移流程示意图

手动故障转移的典型耗时分布:

手动故障转移耗时表

手动操作有一个被低估的优势:人可以做出复杂的情境判断。比如在大促期间,可能会选择先降级部分非核心功能而不是立即切换,以降低切换风险。

但缺点是显而易见的:慢、不可靠、不可复制。凌晨三点和下午三点的操作质量完全不同,资深工程师和新手的操作水平也完全不同。

半自动:脚本化的故障转移

手动操作的下一步演进是将操作步骤封装成脚本。值班工程师仍然需要做决策,但执行过程由脚本完成。

这看起来是个小改进,实际上解决了手动操作中最大的风险:操作失误。脚本不会在凌晨三点敲错命令,不会忘记某个步骤,不会因为紧张而跳过安全检查。

一个典型的半自动故障转移脚本通常包含以下步骤:

  1. 验证主节点确实不可用(多次探测,排除网络抖动)
  2. 评估所有从节点的状态和复制偏移量
  3. 选择最佳候选节点
  4. 执行主从切换(SLAVEOF NO ONE)
  5. 更新其他从节点指向新主节点
  6. 更新 DNS 或 VIP 指向
  7. 验证切换是否成功
  8. 发送通知

半自动的核心价值是把“执行”标准化了,但“决策”仍然留给了人。

这种方式适合那些故障场景相对单一、团队对自动化还没有足够信心的阶段。值班工程师只需要做一个决定:“是否执行切换”,然后按一个按钮,剩下的事情交给脚本。

Redis Sentinel:自动化故障转移的标杆

Redis Sentinel 是缓存领域自动化故障转移的经典实现。它的设计思路对理解故障转移的自动化有很强的参考价值,即使你用的不是 Redis。

Sentinel 的检测机制

Sentinel 使用了两级故障判定机制:

  • 主观下线(SDOWN):单个 Sentinel 节点认为目标不可用。当 Sentinel 在配置的超时时间内(默认 30 秒)没有收到目标的有效回复,就标记目标为主观下线。
  • 客观下线(ODOWN):多个 Sentinel 节点达成共识,认为目标确实不可用。当足够数量(quorum)的 Sentinel 都报告目标为主观下线时,才标记为客观下线,触发故障转移。

Sentinel主观下线与客观下线判定流程图

这种两级机制的精妙之处在于:单个 Sentinel 可能因为自身网络问题误判,但多个独立的 Sentinel 同时误判的概率极低。quorum 的设置让你可以在检测速度和准确性之间做精确的权衡。

Sentinel 的选举过程

当客观下线被确认后,Sentinel 之间需要选出一个 Leader 来执行故障转移。这个选举过程使用了类似 Raft 协议的机制:

  1. 发现主节点客观下线的 Sentinel 发起选举请求
  2. 其他 Sentinel 在同一个 epoch 内只会投一次票
  3. 获得多数票的 Sentinel 成为 Leader
  4. Leader 负责执行后续的故障转移操作

选出 Leader 后,它会按照以下优先级选择新的主节点:

  1. 排除掉已断线、健康度差的从节点
  2. 按照配置的优先级排序(replica-priority)
  3. 优先级相同时,选择复制偏移量最大的(数据最新)
  4. 偏移量也相同时,选择 runid 最小的

Sentinel 的局限

Sentinel 在中等规模的场景下表现很好,但在千万 QPS 的场景下,它有几个值得注意的局限:

  • 切换耗时:从故障发生到切换完成,Sentinel 通常需要 10 到 30 秒。心跳检测超时(默认 30 秒)加上选举和切换本身的耗时,在高 QPS 下这段时间的请求损失不可忽视。
  • 单集群范围:Sentinel 管理的是单个 Redis 主从集群。当你有数百个分片时,每个分片都需要一组 Sentinel,管理复杂度线性增长。
  • 客户端感知延迟:Sentinel 完成切换后,客户端还需要重新发现新主节点,这段延迟取决于客户端的实现。有些客户端通过订阅 Sentinel 的通知频道来感知切换,有些则需要定时轮询。

Redis Cluster:内置的故障转移

Redis Cluster 将故障转移能力内建到了集群协议中,不再依赖外部组件。

Cluster 的故障检测

Redis Cluster 中的每个节点都通过 Gossip 协议与其他节点交换状态信息。当一个节点发现某个主节点不可达时,会标记为 PFAIL(类似 Sentinel 的主观下线)。当集群中超过半数的主节点都将某个节点标记为 PFAIL 时,该节点被标记为 FAIL(客观下线),触发故障转移。

Redis Cluster故障检测与FAIL标记传播示意图

Cluster 的优势

与 Sentinel 相比,Cluster 的故障转移有几个显著优势:

  • 无需额外组件:故障检测和转移的逻辑内建在集群中,不需要部署和维护额外的 Sentinel 节点。
  • 更快的检测速度:Gossip 协议的心跳间隔通常设置为 1 秒,加上 cluster-node-timeout(默认 15 秒),故障检测的总时间通常在 15 到 20 秒。
  • 分片级别的故障隔离:一个分片的主节点故障只影响该分片的 slot 范围,其他分片正常工作。故障转移也是分片级别的,不会影响整个集群。

Redis Cluster 的核心设计理念是:让每个节点都参与故障检测,通过去中心化的共识避免单点故障。

Cluster 的代价

但 Cluster 也不是完美的。故障转移期间,受影响分片上的所有 slot 暂时不可用,客户端会收到 MOVED 或 CLUSTERDOWN 错误。在千万 QPS 的场景下,即使只有一个分片受影响,涉及的 QPS 也可能是数十万级别。

此外,Cluster 的 Gossip 协议在节点数量很多时(比如超过 200 个节点),消息传播和收敛需要更长时间,故障检测的延迟会增加。

代理层故障转移:更快的反应速度

无论是 Sentinel 还是 Cluster,故障检测到切换完成都需要 10 秒以上。对于千万 QPS 的系统,这个时间窗口太长了。

一种更快的方案是在代理层实现故障转移。代理层作为客户端和缓存节点之间的中间层,可以实时感知后端节点的健康状况,在发现故障后立即将流量切换到健康节点。

客户端通过缓存代理访问主从节点架构图

代理层的检测速度

代理层与后端节点之间有持续的连接和请求流量,可以通过以下方式快速检测故障:

  • 连接级检测:TCP 连接断开或 RST 可以在毫秒级被感知。
  • 请求级检测:连续多个请求超时或错误可以触发快速切换。比如设置“连续 3 次请求超时即标记为不可用”,在高 QPS 下这个判断可能在 1 秒内就能完成。
  • 主动探测:代理层定期向后端发送轻量级探测请求(如 PING),超时时间设置得比业务请求更短。

组合使用这些检测手段,代理层的故障检测延迟可以压缩到 1 到 3 秒。

代理层的切换策略

代理层故障转移有两种常见策略:

  • 直接提升模式:代理层检测到主节点故障后,直接将流量切换到从节点。此时从节点以只读模式服务,代理层同时通知管理平面执行正式的主从切换。这种模式的好处是切换速度极快(秒级),但在从节点被正式提升为主节点之前,写请求需要降级处理。
  • 快速重试模式:代理层不做主动切换,而是在请求失败时快速重试到其他节点。如果主节点只是短暂不可用(比如网络抖动),重试可以在几百毫秒内恢复服务,避免不必要的主从切换。

代理层的优势在于“离业务流量最近”,它能比任何外部监控系统更早发现问题,也能更快地做出反应。

代理层的风险

代理层故障转移也有其风险。最大的问题是代理层本身可能误判。一个代理节点与主节点之间的网络出现问题,代理可能认为主节点挂了并切换到从节点,而实际上其他代理节点连接主节点完全正常。这会导致不同的代理节点访问不同的“主”,造成数据分裂。

解决方案通常是引入多代理协商机制:多个代理节点定期交换对后端节点的健康评估,只有当多数代理都认为某个节点不可用时,才触发切换。这本质上和 Sentinel 的 quorum 机制类似,但响应速度更快。

千万 QPS 下的故障转移策略

当系统达到千万 QPS 的规模,故障转移的要求也相应提升。核心指标有两个: 检测时间 (多快发现故障)和 恢复时间 (多快恢复服务)。

分层检测:多道防线

千万 QPS 系统通常采用分层检测的方式,每一层负责不同粒度的故障检测。

缓存故障转移三层检测与响应机制架构图

  • 第一层:代理层。响应最快,但只能做局部判断。主要负责快速重试和临时降级,不做正式的主从切换。
  • 第二层:集群内部。通过 Sentinel 或 Cluster 的内建机制进行故障检测和自动切换。这是主从切换的主力。
  • 第三层:管理平面。全局视角的监控系统,负责跨集群、跨机房的调度决策。比如发现某个机房的多个缓存节点同时出问题,可能是机房级别的故障,需要触发跨机房的流量调度。

降级策略:故障期间的求生之道

在故障检测和切换完成之间的空窗期,系统需要一套降级策略来保障基本可用性:

  • 本地缓存兜底:应用层维护一个本地缓存(如 Caffeine、Guava Cache),当远程缓存不可用时,从本地缓存读取。本地缓存的数据可能不是最新的,但在故障期间“有旧数据总比没数据好”。
  • 限流保护:当缓存故障导致大量请求回源到数据库时,必须对数据库请求进行限流,防止缓存故障引发数据库雪崩。限流策略可以基于令牌桶或滑动窗口实现。
  • 空值缓存:对于查询不到结果的情况,缓存一个短 TTL 的空值,防止缓存穿透加剧数据库压力。

降级策略的目标不是保证服务完全正常,而是在故障期间维持系统的基本可用性,为故障转移争取时间。

预切换:在故障发生之前行动

更激进的策略是预切换:在故障真正发生之前就完成切换。

具体做法是监控节点的健康指标(CPU 使用率、内存使用率、响应延迟的 P99 等),当指标出现异常趋势(比如 P99 延迟持续攀升),在节点完全不可用之前就主动将流量切走。

预切换的好处是避免了故障发生后的被动切换,恢复时间接近零。但风险是可能过于敏感,一个临时的负载波动就触发了不必要的切换。

实践中通常设置多个阈值:

  • 告警阈值:发出告警,值班工程师关注
  • 预切换阈值:自动切走部分流量(比如 50%)
  • 故障阈值:完全切走流量

灰度切换:降低切换风险

无论是自动切换还是预切换,都存在新主节点能否承接全量流量的风险。灰度切换的思路是分阶段转移流量:

  1. 先将 10% 的流量切换到新节点
  2. 观察新节点的响应延迟和错误率
  3. 如果指标正常,逐步增加到 50%、80%、100%
  4. 如果指标异常,立即回滚到旧节点

灰度切换需要代理层支持流量权重分配,增加了实现复杂度,但在千万 QPS 的场景下,这种谨慎是值得的。一次失败的全量切换可能导致新节点瞬间被压垮,造成二次故障。

故障转移中的数据一致性

故障转移不可避免地会带来数据一致性问题。Redis 的主从复制是异步的,主节点故障时,部分数据可能还没有同步到从节点。

异步复制的数据窗口

假设主从复制的延迟是 100 毫秒,主节点的写 QPS 是 50 万,那么在故障发生的瞬间,最多有约 5 万次写操作还没有同步到从节点。这些数据在切换后会丢失。

对于缓存场景,这种数据丢失通常是可以接受的,因为丢失的缓存数据可以从数据库重新加载。但如果缓存被用作临时数据存储(比如分布式锁、会话信息),数据丢失可能会影响业务正确性。

脑裂问题

更严重的问题是脑裂:旧主节点在被判定为故障后又恢复了,此时系统中同时存在两个“主节点”。如果客户端在切换过程中部分连接旧主、部分连接新主,就会出现数据不一致。

Redis Sentinel 通过 epoch 机制来处理脑裂:旧主节点恢复后会发现自己的 epoch 已经过时,自动降级为从节点。但在降级完成之前,仍然存在短暂的脑裂窗口。

Sentinel自动故障转移时序图

故障转移中的数据一致性问题没有完美解决方案,关键是让业务方清楚知道可能的数据丢失窗口,并在架构层面做好容错设计。

不同规模下的故障转移策略

十万 QPS:Sentinel + 半自动就够了

部署三个 Sentinel 节点,配置合理的超时时间(比如 down-after-milliseconds 设置为 5000 到 10000 毫秒)。自动切换处理大部分场景,少数复杂情况由值班工程师通过脚本处理。

端到端恢复时间:15 到 30 秒。这个级别的 QPS 下,30 秒的中断影响有限。

百万 QPS:Cluster + 代理层

使用 Redis Cluster 内建的故障转移能力,配合代理层的快速重试。代理层在节点故障期间提供秒级的降级保护,Cluster 在 15 到 20 秒内完成正式的主从切换。

端到端恢复时间:业务感知约 3 到 5 秒(代理层快速重试);完全恢复约 15 到 20 秒(Cluster 切换完成)。

千万 QPS:分层检测 + 预切换 + 灰度

三层检测机制协同工作。代理层秒级响应提供临时保护,集群内部机制完成正式切换,管理平面做全局调度。对于关键分片,启用预切换和灰度切换策略。

端到端恢复时间:业务感知约 1 到 3 秒(代理层);完全恢复约 10 到 15 秒。

十万/百万/千万QPS级别故障转移策略对比图

故障转移的运维实践

演练:让故障成为日常

自动化故障转移系统最怕的不是故障太多,而是故障太少。如果一个故障转移系统半年都没有触发过,当真正的故障来临时,你对它还有多少信心?

定期的故障演练是必要的。可以通过以下方式模拟故障:

  • 手动 kill 掉主节点进程
  • 使用 iptables 模拟网络分区
  • 注入延迟模拟节点响应变慢
  • 模拟磁盘满导致的持久化失败

每次演练都应该记录完整的时间线:故障注入时间、检测时间、切换开始时间、切换完成时间、客户端恢复时间。这些数据是优化故障转移参数的依据。

故障演练的目标不是验证系统“能不能”自动切换,而是验证它“多快”能切换、切换过程中“有没有”意外。

可观测性:切换过程要透明

故障转移过程中发生了什么,必须有完整的记录。关键指标包括:

  • 故障发生到检测的延迟
  • 选举耗时
  • 切换执行耗时
  • 客户端重连耗时
  • 切换期间的请求错误率和延迟分布
  • 切换后的缓存命中率变化

这些数据不仅用于事后复盘,更重要的是用于持续优化故障转移的参数配置。比如发现心跳超时设置过长导致检测延迟高,或者客户端重连逻辑有 Bug 导致恢复时间超预期。

回滚能力:切换失败的兜底

自动化故障转移最危险的场景是:切换执行了一半,新主节点也挂了。这时候系统处于一个中间状态,既没有完成切换,也回不到切换前的状态。

应对方案是在故障转移流程中设计检查点。每完成一个步骤,验证结果是否符合预期。如果任何步骤失败,按照预定义的回滚流程恢复到上一个稳定状态。

速度与安全的平衡

缓存故障转移从手动到自动的演进,本质上是一个在速度和安全之间不断优化平衡点的过程。

手动转移最安全但最慢,纯自动转移最快但可能误操作。最佳实践通常是分层设计:对于明确的、已知的故障模式,用自动化处理;对于复杂的、罕见的故障模式,保留人工介入的通道。

最理想的故障转移系统,是让 99% 的故障在人感知到之前就已经被自动处理,但为剩下的 1% 留好人工介入的入口。

十万 QPS 的系统,半自动加上好的监控就够了。百万 QPS 需要 Cluster 级别的自动化。千万 QPS 则需要从代理层到集群到管理平面的全栈自动化,这对后端架构的健壮性提出了极高要求。

不管处于哪个阶段,有一件事始终不能省:定期演练。你的系统在 3 个月前还能完成自动切换,不代表今天还能。代码在变,配置在变,基础设施在变。只有持续验证,才能在真正的故障面前保持信心。

最后的问题留给你:你的缓存集群上一次做故障转移演练是什么时候?如果答案是“从来没有”或者“不记得了”,也许这比讨论技术方案更紧迫。欢迎在云栈社区交流你的实践与思考。




上一篇:手淘跨端体验优化实战:FLY-Agent如何整合RAG与SSR渲染实现AI自驱
下一篇:.NET程序员如何应对AI浪潮?理解底层原理与架构比调API更重要
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-7 21:15 , Processed in 0.687994 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表