2. 熔断器状态机

熔断器状态机是降级熔断作业的核心控制单元。它通过三个有限状态——Closed(关闭)、Open(打开)、Half-Open(半开)——以及严格的状态转换条件,实现对下游依赖的自动保护与恢复。理解状态机的行为是进行性能调优和故障演练的前提。

3.1 三种状态的定义与行为

每个状态决定了请求是否被允许通过,以及熔断器内部计数器的行为。下表总结了三种状态的核心特征:

状态 请求是否放行 计数器行为 典型触发条件
Closed 是,正常转发 累计失败次数/比例 系统启动或故障恢复后
Open 否,直接快速失败 启动超时计时器 失败阈值被突破
Half-Open 是,仅限探测请求 记录探测结果 Open 状态超时结束

关键指标:Closed 状态下的失败阈值通常设置为 50% 错误率 或 连续 10 次失败(可配置)。Open 状态的超时时间(熔断恢复等待时间)一般设为 5~30 秒,具体取决于下游恢复速度。

3.2 状态转换条件详解

状态转换由三个核心条件驱动:失败阈值、超时计时器、探测结果。下图展示了完整的转换路径:

熔断器状态机转换图 Closed 请求正常通过 Open 请求快速失败 Half-Open 探测请求放行 失败率 ≥ 阈值 超时计时器到期 探测成功 探测失败

3.3 状态转换的详细流程

熔断器在运行时按照以下步骤进行状态转换:

  1. Closed → Open:熔断器持续统计最近一个时间窗口(如 10 秒)内的请求失败率。当失败率超过配置阈值(如 50%)或连续失败次数达到上限时,熔断器立即跳转到 Open 状态,并启动一个超时计时器。
  2. Open → Half-Open:在 Open 状态下,所有请求被直接拒绝。当超时计时器到期后,熔断器进入 Half-Open 状态,允许有限数量的探测请求通过,以测试下游是否恢复。
  3. Half-Open → Closed 或 Open:如果探测请求成功(如连续 3 次成功),熔断器认为下游已恢复,重置计数器并回到 Closed 状态。如果探测请求失败,熔断器认为下游仍未恢复,立即回到 Open 状态并重置超时计时器。

调优建议:在 Half-Open 状态下,探测请求的数量应设置为 1~5 个。数量过少可能导致误判,数量过多则可能在下游脆弱时造成压力。建议结合下游的吞吐能力动态调整。

3.4 状态转换的配置参数

以下参数直接影响状态转换的灵敏度和系统稳定性:

参数名称 默认值 作用范围 调优方向
失败率阈值 50% Closed → Open 调低可更快熔断,调高可容忍更多抖动
连续失败次数 10 Closed → Open 适用于低流量场景,避免统计窗口过短
熔断超时时间 10 秒 Open → Half-Open 调长可减少探测频率,调短可加速恢复
探测请求数 3 Half-Open → Closed 调高可提高恢复判断的准确性
统计窗口大小 10 秒 Closed 状态 调大可平滑流量波动,调小可快速响应

3.5 状态机与性能调优的关联

状态机的转换频率和停留时间直接影响系统的吞吐量和响应时间:

  • 频繁的 Open/Closed 切换:表明阈值设置过于敏感,会导致系统在正常波动下反复熔断,降低可用性。应适当提高失败率阈值或延长统计窗口。
  • Open 状态持续时间过长:会导致下游恢复后无法及时承接流量,造成资源浪费。应适当缩短熔断超时时间,或采用自适应超时策略。
  • Half-Open 状态探测失败率过高:说明下游恢复不稳定,应增加探测请求数或延长超时时间,避免过早放行大量请求。

避坑指南:不要将熔断超时时间设置得比下游的平均恢复时间短。例如,如果下游数据库故障恢复通常需要 30 秒,而熔断超时设为 5 秒,则会导致频繁的 Open→Half-Open→Open 循环,产生“熔断抖动”现象,反而增加系统负载。

3.6 状态机实现示例(伪代码)

以下伪代码展示了状态机在熔断器核心逻辑中的实现:

class CircuitBreaker {
    state = CLOSED
    failureCount = 0
    lastFailureTime = 0
    timeout = 10000  // 10秒超时
    threshold = 10   // 连续失败阈值

    function call(request) {
        if (state == OPEN) {
            if (currentTime - lastFailureTime > timeout) {
                state = HALF_OPEN
            } else {
                return FAIL_FAST
            }
        }

        if (state == HALF_OPEN) {
            result = sendProbeRequest(request)
            if (result == SUCCESS) {
                state = CLOSED
                failureCount = 0
                return result
            } else {
                state = OPEN
                lastFailureTime = currentTime
                return FAIL_FAST
            }
        }

        // state == CLOSED
        result = sendRequest(request)
        if (result == FAILURE) {
            failureCount++
            if (failureCount >= threshold) {
                state = OPEN
                lastFailureTime = currentTime
            }
        } else {
            failureCount = 0
        }
        return result
    }
}