从零实现一个断路器
通过手动实现一个简化版的断路器,可以深入理解其工作原理。以下是完整的 TypeScript 实现。
定义配置类型
// circuit-breaker.interface.ts
export interface CircuitBreakerOptions {
timeout: number; // 请求超时时间(毫秒)
resetTimeout: number; // 熔断后恢复等待时间(毫秒)
errorThresholdPercentage: number; // 熔断阈值(百分比)
fallback?: (...args: any[]) => any; // 降级回调函数
}
typescript
实现断路器类
// circuit-breaker.service.ts
export enum CircuitState {
CLOSED = 'CLOSED', // 正常
OPEN = 'OPEN', // 熔断中
HALF_OPEN = 'HALF_OPEN', // 半开(尝试恢复)
}
export class CircuitBreaker {
private state: CircuitState = CircuitState.CLOSED;
private failureCount = 0;
private successCount = 0;
private totalCount = 0;
private lastFailureTime: number | null = null;
constructor(private readonly options: CircuitBreakerOptions) {}
// 执行被保护的方法
async fire<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === CircuitState.OPEN) {
// 检查是否到达恢复时间
if (this.shouldAttemptReset()) {
this.state = CircuitState.HALF_OPEN;
} else {
// 仍然在熔断中,执行降级
return this.executeFallback<T>();
}
}
try {
// 带超时执行请求
const result = await this.executeWithTimeout<T>(fn);
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
return this.executeFallback<T>();
}
}
// 带超时执行异步方法
private async executeWithTimeout<T>(fn: () => Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`请求超时 (${this.options.timeout}ms)`));
}, this.options.timeout);
fn()
.then(result => {
clearTimeout(timer);
resolve(result);
})
.catch(error => {
clearTimeout(timer);
reject(error);
});
});
}
// 成功回调
private onSuccess(): void {
this.failureCount = 0;
this.successCount++;
this.totalCount++;
if (this.state === CircuitState.HALF_OPEN) {
// 半开状态下成功,恢复为关闭状态
this.state = CircuitState.CLOSED;
this.resetCounters();
}
}
// 失败回调
private onFailure(): void {
this.failureCount++;
this.totalCount++;
this.lastFailureTime = Date.now();
if (this.state === CircuitState.HALF_OPEN) {
// 半开状态下失败,重新打开熔断
this.state = CircuitState.OPEN;
} else if (this.shouldTrip()) {
// 达到熔断阈值,打开熔断
this.state = CircuitState.OPEN;
}
}
// 判断是否达到熔断阈值
private shouldTrip(): boolean {
if (this.totalCount < 5) return false; // 最少 5 次请求才统计
const errorPercentage = (this.failureCount / this.totalCount) * 100;
return errorPercentage >= this.options.errorThresholdPercentage;
}
// 判断是否应该尝试恢复
private shouldAttemptReset(): boolean {
if (!this.lastFailureTime) return false;
return Date.now() - this.lastFailureTime >= this.options.resetTimeout;
}
// 执行降级逻辑
private executeFallback<T>(): T {
if (this.options.fallback) {
return this.options.fallback();
}
throw new Error('服务不可用,熔断中');
}
// 重置计数器
private resetCounters(): void {
this.failureCount = 0;
this.successCount = 0;
this.totalCount = 0;
}
// 获取当前状态(用于监控)
getState(): { state: CircuitState; failureCount: number; totalCount: number } {
return {
state: this.state,
failureCount: this.failureCount,
totalCount: this.totalCount,
};
}
}
typescript
在 NestJS 中使用
// app.service.ts
import { Injectable } from '@nestjs/common';
import { CircuitBreaker } from './circuit-breaker.service';
@Injectable()
export class AppService {
private breaker: CircuitBreaker;
constructor() {
this.breaker = new CircuitBreaker({
timeout: 3000, // 3 秒超时
resetTimeout: 30000, // 30 秒后尝试恢复
errorThresholdPercentage: 50, // 错误率超过 50% 时熔断
fallback: () => ({ message: '服务暂不可用,请稍后重试' }),
});
}
async callRemoteService() {
return this.breaker.fire(async () => {
const response = await fetch('http://service-b/api/data');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
});
}
}
typescript
状态监控端点
// circuit-breaker.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('circuit-breaker')
export class CircuitBreakerController {
constructor(private readonly appService: AppService) {}
@Get('status')
getStatus() {
return this.appService.getBreakerState();
}
}
typescript
实现要点总结
这个最小实现涵盖了断路器的所有核心要素:
| 要素 | 实现 |
|---|---|
| 三种状态 | CLOSED → OPEN → HALF_OPEN 的状态机 |
| 超时控制 | Promise.race 实现请求超时 |
| 熔断阈值 | 失败率超过指定百分比时触发 |
| 恢复机制 | resetTimeout 后进入半开状态尝试恢复 |
| 降级策略 | 触发熔断时执行 fallback 函数 |
| 最小请求数 | 请求次数少于 5 次时不统计失败率,避免误判 |
在生产环境中,建议使用成熟的 Opossum 库(下节介绍)而不是自实现,因为它经过大量生产验证,功能更完善。
↑