circuit-breaker.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /**
  2. * 熔断器 - 供应商级别的故障隔离
  3. *
  4. * 状态机: CLOSED → (N次连续失败) → OPEN → (冷却期过后) → HALF_OPEN → (成功) → CLOSED
  5. *
  6. * 使用方式:
  7. * const breaker = new CircuitBreaker({ name: 'minimax', failureThreshold: 3, cooldownMs: 30000 });
  8. * try {
  9. * await breaker.call(async () => { ... });
  10. * } catch (e) {
  11. * // 熔断开启或调用失败
  12. * }
  13. */
  14. export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
  15. export interface CircuitBreakerConfig {
  16. name: string;
  17. failureThreshold?: number; // 连续失败多少次后熔断,默认 3
  18. cooldownMs?: number; // 熔断冷却时间,默认 30000ms
  19. successThreshold?: number; // HALF_OPEN 状态下需要连续成功多少次才恢复,默认 2
  20. }
  21. export class CircuitBreaker {
  22. readonly name: string;
  23. private failureThreshold: number;
  24. private cooldownMs: number;
  25. private successThreshold: number;
  26. private state: CircuitState = 'CLOSED';
  27. private failureCount = 0;
  28. private successCount = 0;
  29. private openedAt = 0;
  30. constructor(config: CircuitBreakerConfig) {
  31. this.name = config.name;
  32. this.failureThreshold = config.failureThreshold ?? 3;
  33. this.cooldownMs = config.cooldownMs ?? 30000;
  34. this.successThreshold = config.successThreshold ?? 2;
  35. }
  36. getState(): CircuitState {
  37. // 如果 OPEN 且冷却期已过,自动进入 HALF_OPEN
  38. if (this.state === 'OPEN' && Date.now() - this.openedAt >= this.cooldownMs) {
  39. this.state = 'HALF_OPEN';
  40. this.failureCount = 0;
  41. this.successCount = 0;
  42. console.log(`[熔断器] ${this.name}: OPEN → HALF_OPEN(冷却期已过)`);
  43. }
  44. return this.state;
  45. }
  46. isOpen(): boolean {
  47. return this.getState() === 'OPEN';
  48. }
  49. /**
  50. * 执行受保护的操作
  51. * 返回操作成功的结果,抛出 CircuitBreakerOpenError 或原始错误
  52. */
  53. async call<T>(fn: () => Promise<T>): Promise<T> {
  54. const currentState = this.getState();
  55. if (currentState === 'OPEN') {
  56. const remainingMs = this.cooldownMs - (Date.now() - this.openedAt);
  57. throw new CircuitBreakerOpenError(this.name, remainingMs);
  58. }
  59. try {
  60. const result = await fn();
  61. this.onSuccess();
  62. return result;
  63. } catch (error) {
  64. this.onFailure();
  65. throw error;
  66. }
  67. }
  68. private onSuccess(): void {
  69. if (this.state === 'HALF_OPEN') {
  70. this.successCount++;
  71. if (this.successCount >= this.successThreshold) {
  72. this.state = 'CLOSED';
  73. this.failureCount = 0;
  74. this.successCount = 0;
  75. console.log(`[熔断器] ${this.name}: HALF_OPEN → CLOSED(恢复)`);
  76. }
  77. } else {
  78. // CLOSED 状态,重置失败计数
  79. this.failureCount = 0;
  80. }
  81. }
  82. private onFailure(): void {
  83. this.failureCount++;
  84. this.successCount = 0;
  85. if (this.state === 'HALF_OPEN' || (this.state === 'CLOSED' && this.failureCount >= this.failureThreshold)) {
  86. this.state = 'OPEN';
  87. this.openedAt = Date.now();
  88. console.log(`[熔断器] ${this.name}: → OPEN(连续失败 ${this.failureCount} 次,冷却 ${this.cooldownMs}ms)`);
  89. }
  90. }
  91. /** 手动重置 */
  92. reset(): void {
  93. this.state = 'CLOSED';
  94. this.failureCount = 0;
  95. this.successCount = 0;
  96. this.openedAt = 0;
  97. }
  98. }
  99. export class CircuitBreakerOpenError extends Error {
  100. public readonly breakerName: string;
  101. public readonly remainingMs: number;
  102. constructor(name: string, remainingMs: number) {
  103. super(`[${name}] 熔断器已开启,剩余冷却时间 ${Math.ceil(remainingMs / 1000)}s`);
  104. this.name = 'CircuitBreakerOpenError';
  105. this.breakerName = name;
  106. this.remainingMs = remainingMs;
  107. }
  108. }