跳转到内容

熔断器模式与故障转移

熔断器模式与故障转移

前置知识

本章内容属于分布式系统可靠性模式的通用知识,与具体的 AI 项目无关。建议了解过基本的 HTTP 代理服务概念后再阅读。


学习目标

  • 理解熔断器模式的三态(Closed / Open / HalfOpen)转换逻辑
  • 掌握熔断器配置的关键参数:失败阈值、成功阈值、超时时间、错误率阈值
  • 了解熔断器在 AI Provider 故障转移中的具体应用
  • 识别并发场景下的熔断器陷阱与防护策略

项目实践

核心问题

当 AI 客户端向多个 API 供应商发送请求时,某个供应商可能突然不可用(超时、5xx 错误、认证失败)。如果不做任何处理,客户端会持续向故障供应商发送请求,导致:

  • 请求持续失败,用户长时间等待超时
  • 浪费用户的 Token 配额(失败的请求也可能计费)
  • 拖慢整体响应速度

熔断器模式正是为解决这个问题而设计的:当检测到某个供应商连续失败时,自动”跳闸”,拒绝向其发送新请求,一段时间后再尝试恢复。

三态转换模型

熔断器有三种状态:

Closed(关闭状态)——正常工作

熔断器关闭,允许所有请求正常通过。同时记录:

  • 连续失败计数:每次失败 +1,成功时重置为 0
  • 总请求数 / 失败数:用于计算错误率

触发打开的条件有两个(满足任一即打开):

  1. 连续失败阈值:连续失败次数达到 failure_threshold(默认 4 次)
  2. 错误率阈值:在至少 min_requests(默认 10 次)请求后,错误率超过 error_rate_threshold(默认 60%)

双阈值设计的意义:连续失败阈值用于快速响应突发故障,错误率阈值用于捕捉”间歇性失败”的慢性问题。

Open(打开状态)——熔断激活

熔断器打开后,拒绝所有新请求,立即返回失败或切换到备用 Provider。

同时记录 last_opened_at(打开时间)。当当前时间超过 timeout_seconds(默认 60 秒)时,转换到 HalfOpen 状态。

HalfOpen(半开状态)——尝试恢复

半开状态是 Open 到 Closed 的过渡阶段。此时只允许有限数量的探测请求通过(通常只允许 1 个),观察是否成功:

  • 探测成功:累计成功次数,达到 success_threshold(默认 2 次)后转回 Closed
  • 探测失败:立即转回 Open,重新开始计时

半开状态的探测请求限额设计是关键:如果放开所有请求,一旦供应商仍不可用,会瞬间产生大量失败请求。

配置参数详解

CircuitBreakerConfig {
failure_threshold: 4, // 连续失败多少次后打开
success_threshold: 2, // 半开状态下成功多少次后关闭
timeout_seconds: 60, // 打开后多久尝试半开
error_rate_threshold: 0.6, // 错误率阈值 (0.0-1.0)
min_requests: 10, // 计算错误率前的最小请求数
}

参数调优指南

场景failure_thresholdtimeout_seconds理由
高延迟 API6-8120+偶尔超时不等于故障,给更多容忍度
关键业务2-330快速隔离故障,缩短不可用时间
不稳定但有自愈5180+给供应商充分的自愈时间

并发安全设计

多线程环境下,熔断器的状态转换必须是原子的。典型实现使用 AtomicU32 管理计数器,RwLock 保护状态转换:

pub struct CircuitBreaker {
state: Arc<RwLock<CircuitState>>, // 状态由 RwLock 保护
consecutive_failures: Arc<AtomicU32>, // 计数器用原子操作
consecutive_successes: Arc<AtomicU32>,
half_open_requests: Arc<AtomicU32>, // 半开探测名额限流
// ...
}

HalfOpen 探测名额的并发陷阱

在半开状态到 Closed 状态转换的过程中,如果外部再次调用 transition_to_half_open,可能重置正在使用的探测名额计数器。正确的做法是:转换操作必须检查当前状态,非预期状态下直接返回,不重置计数器


问题与规避

陷阱 1:熔断器打开期间请求全部丢失

问题:Open 状态下所有请求都被拒绝,如果只有一个 Provider,用户将完全无法使用。

规避策略

  • 故障转移队列:维护一个备用 Provider 列表,熔断后自动切换到队列中的下一个可用 Provider
  • 优雅降级:即使没有备用 Provider,也应返回明确的错误信息(如”供应商暂时不可用,请稍后重试”),而非直接超时

陷阱 2:错误率计算的分母为零

问题:如果 min_requests 设置过大,而实际请求量很少,错误率永远不会被计算。

规避策略

  • min_requests 不应超过 failure_threshold 的 2-3 倍
  • 在 Provider 切换频繁的场景中,考虑使用滑动窗口而非全局计数器

陷阱 3:半开状态探测成功但实际未恢复

问题:供应商可能只是短暂恢复了一两个请求,随后又进入故障状态。

规避策略

  • 设置 success_threshold >= 2,要求多次连续成功才认定恢复
  • 在半开状态下限制探测请求的并发数,避免”一次性全量请求”导致的误判

陷阱 4:熔断器状态与应用生命周期不一致

问题:应用重启后,内存中的熔断器状态丢失,可能在供应商仍未恢复时重新开始发送请求。

规避策略

  • 将熔断器状态持久化到数据库(序列化后存储)
  • 启动时加载上次状态,或至少在启动时进行一次健康检测再决定是否打开熔断器

设计取舍

为什么选择熔断器而非简单重试?

方案优势劣势
简单重试实现简单持续浪费资源,无法区分暂时性故障和持久性故障
退避重试缓解服务器压力仍会持续向故障端点发送请求
熔断器快速隔离故障,保护资源,支持自动恢复实现复杂,需要状态管理

熔断器的核心价值在于:它不只是”重试策略”,而是”隔离策略”。当某个供应商完全不可用时,熔断器阻止后续请求到达它,保护客户端的资源不被浪费。

替代方案:健康检测轮询

另一种方案是定期对每个 Provider 做健康检测,不健康的直接标记为不可用。

维度熔断器健康检测轮询
响应速度实时(请求失败即触发)取决于轮询间隔
额外开销无(基于实际请求判断)有(轮询请求产生额外流量)
恢复速度自动(超时后半开探测)取决于轮询间隔
实现复杂度中等

实践中常将两者结合:熔断器负责快速隔离,健康检测负责主动发现和恢复。

替代方案:自适应负载均衡

更高级的方案是自适应负载均衡——根据每个后端的历史延迟和成功率动态分配流量。这比熔断器更智能,但实现复杂度也更高。对于 Provider 数量较少(通常 3-5 个)的场景,熔断器 + 故障转移队列的组合已经足够。


参考来源