2. 熔断器三种状态详解:关闭、打开与半开

熔断器(Circuit Breaker)的核心在于其有限状态机模型。理解 关闭(Closed)、打开(Open) 和 半开(Half-Open) 三种状态的行为与转换条件,是驱动开发中正确实现故障隔离与自动恢复的基础。下面从状态行为、转换触发条件和典型时序三个维度进行剖析。

2.1 状态总览与核心差异

三种状态构成了一个闭环的状态机。下表从 请求放行策略、故障计数器行为 和 超时/恢复机制 三个关键维度进行对比:

状态 请求放行策略 故障计数器行为 超时/恢复机制 典型应用场景
关闭(Closed) 全部放行 累计失败次数;成功时重置 无超时;达到阈值即转换 正常运行时,系统稳定
打开(Open) 全部拒绝(快速失败) 冻结计数器 启动超时计时器(如 30s) 下游严重故障,需要冷却
半开(Half-Open) 放行探测请求(通常 1 个) 重置或单独计数 等待探测结果 试探下游是否恢复

2.2 关闭状态(Closed)—— 正常通行

关闭状态是熔断器的初始状态,也是系统正常运行时的默认状态。在此状态下,熔断器对调用方完全透明,所有请求均被放行至下游服务或硬件设备。

  • 行为特征:请求计数器持续累加;若请求成功,失败计数器归零;若请求失败,失败计数器递增。
  • 转换条件:当失败计数器在指定时间窗口内(如 10 秒内连续 5 次失败)达到预设阈值时,状态从 Closed → Open。
  • 驱动开发要点:在驱动层,Closed 状态通常对应一个 atomic_t 或 spinlock 保护的失败计数变量。每次 I/O 操作完成后,驱动需在中断上下文或工作队列中更新该计数器。
实践建议:在驱动中实现 Closed 状态时,务必使用无锁或自旋锁保护计数器,避免在高速 I/O 路径上引入过多延迟。推荐使用 atomic_inc_unless_negative() 等原子操作。

2.3 打开状态(Open)—— 快速失败

当熔断器进入打开状态,意味着系统判定下游已发生严重或持续故障。此时熔断器会立即拒绝所有请求,避免调用方继续等待或加重下游负载。

  • 行为特征:所有请求直接返回错误(如 -ECONNREFUSED 或自定义错误码);不执行实际的 I/O 操作。
  • 转换条件:打开状态会启动一个 超时计时器(例如 30 秒)。计时器到期后,状态自动从 Open → Half-Open。
  • 驱动开发要点:在驱动中,Open 状态通常使用 hrtimer 或 timer_list 实现超时回调。超时时间应可配置,且建议采用 指数退避 策略(如第一次 10s,第二次 20s,第三次 40s)。
注意:打开状态下的“快速失败”必须与业务层的超时区分开。驱动层应返回明确的硬件错误码,避免调用方误判为“超时”或“资源忙”。

2.4 半开状态(Half-Open)—— 试探恢复

半开状态是熔断器从故障中恢复的关键中间态。它允许有限数量的探测请求通过,以验证下游是否已恢复正常。

  • 行为特征:仅放行一个(或少量)探测请求;其余请求仍被拒绝。探测请求的结果决定下一步状态转换。
  • 转换条件:
    • 若探测请求 成功:状态从 Half-Open → Closed(完全恢复)。
    • 若探测请求 失败:状态从 Half-Open → Open(重新冷却,并重置超时计时器)。
  • 驱动开发要点:半开状态需要实现一个“探测请求计数器”或“探测锁”。驱动中通常使用 atomic_t 或 test_and_set_bit() 来保证只有一个探测请求进入 I/O 路径。

2.5 状态转换流程图

下图展示了三种状态之间的完整转换路径,以及触发转换的关键条件:

熔断器三种状态转换图 关闭 (Closed) 全部请求放行 打开 (Open) 全部请求拒绝 半开 (Half-Open) 探测请求放行 失败次数达到阈值 超时计时器到期 探测请求成功 探测请求失败 说明:半开状态是恢复的关键;探测请求成功则闭环恢复,失败则重新打开并重置超时。

2.6 状态转换条件汇总

下表以驱动开发中常见的 条件表达式 形式,归纳了三种状态之间的转换逻辑:

当前状态 转换条件 目标状态 驱动实现要点
Closed failure_count >= threshold Open 在 I/O 完成回调中检查计数器;使用 atomic_add_unless()
Open timeout_expired == true Half-Open 使用 hrtimer_start() 启动超时;回调中设置状态标志
Half-Open probe_success == true Closed 重置失败计数器;恢复全部请求放行
Half-Open probe_failure == true Open 重新启动超时计时器;冻结计数器

2.7 半开状态在驱动中的特殊处理

半开状态是三种状态中 最复杂 的一个,因为它涉及并发控制与探测请求的原子性。在驱动开发中,需要特别注意以下几点:

  • 探测请求的原子性:必须保证同一时刻只有一个探测请求进入 I/O 路径。可以使用 test_and_set_bit() 或 atomic_xchg() 实现探测锁。
  • 探测超时:探测请求本身也应设置超时(通常短于正常请求超时),避免探测请求阻塞导致状态机死锁。
  • 计数器重置策略:进入半开状态时,建议将失败计数器重置为 0,避免历史失败数据影响探测结果。
关键设计模式:在驱动中实现半开状态时,推荐使用 状态模式(State Pattern) 或 有限状态机(FSM) 框架。将每个状态封装为独立的结构体,通过函数指针表实现状态转换,避免大量的 switch-case 分支。

2.8 状态转换时序图

下面通过一个时序图展示从 Closed → Open → Half-Open → Closed 的完整生命周期,以及各阶段的关键事件:

熔断器状态转换时序(完整恢复周期) Closed Open Half-Open → 连续失败达到阈值 超时计时器到期 探测请求成功 关键时间点说明 T1: 失败计数器达到阈值,状态从 Closed → Open T2: 超时计时器到期,状态从 Open → Half-Open T3: 探测请求成功,状态从 Half-Open → Closed(恢复完成)

2.9 驱动代码示例:状态转换核心逻辑

以下是一个简化的驱动级熔断器状态转换实现,展示了三种状态的核心判断逻辑:

/* 熔断器状态枚举 */
enum cb_state {
    CB_CLOSED,
    CB_OPEN,
    CB_HALF_OPEN,
};

/* 熔断器上下文结构体 */
struct cb_context {
    enum cb_state       state;
    atomic_t            fail_count;
    unsigned int        threshold;
    unsigned long       timeout_ms;
    struct hrtimer      timer;
    spinlock_t          lock;
};

/* 状态转换核心函数 */
static void cb_state_transition(struct cb_context *cb, bool request_success)
{
    unsigned long flags;

    spin_lock_irqsave(&cb->lock, flags);

    switch (cb->state) {
    case CB_CLOSED:
        if (!request_success) {
            if (atomic_inc_return(&cb->fail_count) >= cb->threshold) {
                cb->state = CB_OPEN;
                /* 启动超时计时器 */
                hrtimer_start(&cb->timer, ms_to_ktime(cb->timeout_ms),
                              HRTIMER_MODE_REL);
                atomic_set(&cb->fail_count, 0);
            }
        } else {
            atomic_set(&cb->fail_count, 0);
        }
        break;

    case CB_OPEN:
        /* 打开状态不处理请求,直接返回错误 */
        break;

    case CB_HALF_OPEN:
        if (request_success) {
            cb->state = CB_CLOSED;
            atomic_set(&cb->fail_count, 0);
        } else {
            cb->state = CB_OPEN;
            /* 重新启动超时计时器(指数退避) */
            hrtimer_start(&cb->timer, ms_to_ktime(cb->timeout_ms * 2),
                          HRTIMER_MODE_REL);
        }
        break;
    }

    spin_unlock_irqrestore(&cb->lock, flags);
}

/* 超时回调:从 Open 切换到 Half-Open */
static enum hrtimer_restart cb_timeout_callback(struct hrtimer *timer)
{
    struct cb_context *cb = container_of(timer, struct cb_context, timer);

    spin_lock(&cb->lock);
    if (cb->state == CB_OPEN)
        cb->state = CB_HALF_OPEN;
    spin_unlock(&cb->lock);

    return HRTIMER_NORESTART;
}
代码解读:上述实现中,cb_state_transition() 在每次 I/O 完成后被调用。Closed 状态使用原子变量 fail_count 记录失败次数;Open 状态通过 hrtimer 实现超时;Half-Open 状态根据探测请求结果决定恢复或重新打开。注意 spinlock 保护了状态切换的原子性。