← 中文资源

简体中文版本

熔断器状态机

用关闭、打开、半开三种状态跟踪依赖故障,连续失败后快速拒绝请求,冷却结束再放行一次试探。

阅读英文原文

功能说明

下游服务持续超时或报错时,继续重试只会拖垮调用方线程池和连接池。熔断器在连续失败达到阈值后进入打开状态,所有请求立即被拒绝,不再访问故障依赖;冷却时间结束后进入半开状态,只放行一次试探请求,成功则恢复正常,失败则重新打开并延长等待。

本例用枚举表示三种状态,用失败计数、阈值和冷却时长驱动转换。allow 决定当前请求能否执行,record 在请求结束后记录成功或失败。演示连续四次请求中前两次被允许且失败,随后两次被快速拒绝;等待冷却后再放行一次,最后记录成功使熔断器复位。

关键代码

State 派生 Copy 和 PartialEq,便于在 match 与条件中直接使用。allow 在打开状态下检查 opened_at 的经过时间,达到冷却就切换到半开并允许一次。半开状态再次调用 allow 会拒绝,防止并发涌入,真实实现应为半开设置单飞锁。record 在成功时清零失败并关闭;失败时累加计数,达到阈值或在半开中失败就打开并刷新计时。主程序只在请求被允许时调用 record,避免被拒绝的请求延长冷却。

使用注意

熔断器应按依赖和操作分别创建,不能用一个实例覆盖所有下游。阈值和冷却时间需要根据错误率与恢复速度调优,固定阈值无法适应流量波动。应区分可熔断错误与业务错误,参数校验失败不应计入故障。半开状态必须限制并发试探数,否则恢复瞬间仍可能压垮依赖。熔断只是保护手段,还应配合超时、重试预算和降级响应。被拒绝的请求不要调用 record,否则会不断重置冷却。

验证方式

运行脚本,前两次 allowed 为 true,随后两次为 false,说明阈值二触发打开。等待六十毫秒后输出 after cooldown allowed=true,进入半开试探;若把最后一行改为记录失败,再次调用 allow 应立即返回 false。将阈值改为一,可看到第一次失败后就直接打开。

代码示例

use std::time::{Duration, Instant};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
    Closed,
    Open,
    HalfOpen,
}

struct CircuitBreaker {
    state: State,
    failures: u32,
    threshold: u32,
    cooldown: Duration,
    opened_at: Instant,
}

enum Decision {
    Allow,
    Reject,
}

impl CircuitBreaker {
    fn new(threshold: u32, cooldown: Duration) -> Self {
        Self {
            state: State::Closed,
            failures: 0,
            threshold,
            cooldown,
            opened_at: Instant::now(),
        }
    }

    fn allow(&mut self) -> Decision {
        match self.state {
            State::Closed => Decision::Allow,
            State::Open if self.opened_at.elapsed() >= self.cooldown => {
                self.state = State::HalfOpen;
                Decision::Allow
            }
            State::Open => Decision::Reject,
            State::HalfOpen => Decision::Reject,
        }
    }

    fn record(&mut self, success: bool) {
        if success {
            self.state = State::Closed;
            self.failures = 0;
            return;
        }
        self.failures += 1;
        if self.failures >= self.threshold || self.state == State::HalfOpen {
            self.state = State::Open;
            self.opened_at = Instant::now();
        }
    }
}

fn main() {
    let mut breaker = CircuitBreaker::new(2, Duration::from_millis(50));
    for attempt in 0..4 {
        let allowed = matches!(breaker.allow(), Decision::Allow);
        println!("attempt {attempt}: allowed={allowed}");
        if allowed {
            breaker.record(false);
        }
    }
    std::thread::sleep(Duration::from_millis(60));
    let trial = matches!(breaker.allow(), Decision::Allow);
    println!("after cooldown allowed={trial}");
    breaker.record(true);
}