熔断器

熔断器模式配置参考和模拟,理解Circuit Breaker的工作原理

🔧 配置生成
🎮 模拟器
📖 设计模式
💻 实现代码
CLOSED
正常
失败率超阈值 →
OPEN
熔断
超时后 →
HALF-OPEN
半开
超过此阈值触发熔断
统计窗口内的请求数
OPEN状态持续时间
HALF-OPEN状态允许的试探请求数
超过此时间视为慢调用
慢调用占比超此值触发熔断

熔断器三状态

CLOSED (关闭/正常)
  → 请求正常通过
  → 统计失败率
  → 失败率超阈值 → 切换到 OPEN

OPEN (打开/熔断)
  → 请求直接拒绝(快速失败)
  → 返回降级响应或错误
  → 超时后 → 切换到 HALF-OPEN

HALF-OPEN (半开)
  → 允许少量请求通过(试探)
  → 试探成功 → 切换到 CLOSED
  → 试探失败 → 切换回 OPEN

降级策略

1. 返回缓存数据
   → 返回上次成功的缓存结果

2. 返回默认值
   → 返回安全的默认响应

3. 返回精简数据
   → 返回核心功能,跳过增强功能

4. 队列化请求
   → 将请求放入队列,稍后重试

5. 快速失败
   → 直接返回错误,让调用方处理

与其他模式配合

熔断器 + 重试
  → 重试在熔断器之前
  → 重试失败后才计入熔断器统计

熔断器 + 限流
  → 限流在熔断器之前
  → 限流拒绝不计入熔断器统计

熔断器 + 舱壁
  → 每个下游服务独立熔断器
  → 一个服务熔断不影响其他服务

Node.js 实现

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 50;
    this.windowSize = options.windowSize || 10;
    this.openDuration = options.openDuration || 30000;
    this.halfOpenRequests = options.halfOpenRequests || 3;
    this.state = 'CLOSED';
    this.failures = 0;
    this.successes = 0;
    this.openedAt = null;
    this.halfOpenCount = 0;
  }

  async execute(fn, fallback) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.openedAt > this.openDuration) {
        this.state = 'HALF-OPEN';
        this.halfOpenCount = 0;
      } else {
        return fallback ? fallback() : 
          Promise.reject(new Error('Circuit is OPEN'));
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      return fallback ? fallback(error) : Promise.reject(error);
    }
  }

  onSuccess() {
    if (this.state === 'HALF-OPEN') {
      this.halfOpenCount++;
      if (this.halfOpenCount >= this.halfOpenRequests) {
        this.state = 'CLOSED';
        this.failures = 0;
        this.successes = 0;
      }
    }
    this.successes++;
    this.failures = 0;
  }

  onFailure() {
    this.failures++;
    if (this.state === 'HALF-OPEN') {
      this.state = 'OPEN';
      this.openedAt = Date.now();
      return;
    }
    const total = this.failures + this.successes;
    if (total >= this.windowSize) {
      const rate = (this.failures / total) * 100;
      if (rate >= this.failureThreshold) {
        this.state = 'OPEN';
        this.openedAt = Date.now();
      }
    }
  }
}

// 使用
const breaker = new CircuitBreaker({
  failureThreshold: 50,
  windowSize: 10,
  openDuration: 30000,
});

const result = await breaker.execute(
  () => fetch('/api/data').then(r => r.json()),
  () => ({ data: 'cached-fallback' })
);

Resilience4j (Java)

CircuitBreakerConfig config = CircuitBreakerConfig.custom()
  .failureRateThreshold(50)
  .slidingWindowSize(10)
  .waitDurationInOpenState(Duration.ofSeconds(30))
  .permittedNumberOfCallsInHalfOpenState(3)
  .slowCallDurationThreshold(Duration.ofSeconds(3))
  .slowCallRateThreshold(80)
  .build();

CircuitBreaker breaker = CircuitBreaker.of("api", config);

Supplier<String> supplier = CircuitBreaker
  .decorateSupplier(breaker, () -> apiClient.getData());

String result = Try.ofSupplier(supplier)
  .recover(throwable -> "fallback").get();

Polly (.NET)

var breaker = Policy
  .Handle<HttpRequestException>()
  .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
  .CircuitBreakerAsync(
    exceptionsAllowedBeforeBreaking: 5,
    durationOfBreak: TimeSpan.FromSeconds(30),
    onBreak: (ex, breakDelay) => 
      Console.WriteLine($"Circuit OPEN for {breakDelay.TotalSeconds}s"),
    onReset: () => Console.WriteLine("Circuit CLOSED")
  );

var result = await breaker.ExecuteAsync(async () => {
  var response = await httpClient.GetAsync("/api/data");
  response.EnsureSuccessStatusCode();
  return response;
});