AbortController 实现带熔断和指数退避的请求网关,核心是解耦请求控制、失败统计与重试决策:用 AbortSignal 统一取消入口,轻量熔断器基于滑动窗口状态机,指数退避结合 jitter 与 signal 中止,最终通过 gateway.request 声明式组合全流程。

用 AbortController 实现带熔断和指数退避的请求网关,核心在于把“请求生命周期控制”、“失败状态统计”和“重试策略决策”三者解耦并协同。AbortController 本身不处理熔断或退避,但它提供了标准、可组合的取消能力——这是整个网关可靠性的基石。
用 AbortSignal 封装请求上下文,统一取消入口
每次发起请求前,创建独立的 AbortController,并将 signal 透传给 fetch 或其他底层客户端(如 axios)。关键不是只用来手动取消,而是让所有异步链路(包括重试定时器、超时逻辑、熔断检查)都能响应同一个 signal。
- 封装一个 requestWithSignal 工具函数,内部调用 fetch 并监听 signal.aborted;一旦中止,主动 reject 带 AbortError 的 Promise
- 在重试逻辑中,每次新尝试都新建 AbortController,但上层可传入一个“全局 signal”用于强制终止整条重试链
- 避免复用同一个 AbortController 多次调用 abort() —— 它只能触发一次,重复调用无效
实现轻量级熔断器:基于失败窗口与状态机
熔断不是开关,而是一个有状态的滑动窗口计数器。不需要引入外部库,用 Map + setTimeout 即可维护最近 N 秒内的请求结果。
- 为每个唯一 URL(或 service ID)维护一个 熔断状态对象:{ state: 'CLOSED' | 'OPEN' | 'HALF_OPEN', failureCount: number, windowStart: number, timeoutId?: NodeJS.Timeout }
- CLOSED 状态下正常发请求;失败时记录时间戳并累加计数;当单位时间内失败率超阈值(如 5 次/10s 且失败率 ≥ 70%),自动切到 OPEN
- OPEN 状态下直接 reject,不发请求;同时设置一个定时器,在超时后(如 30s)自动切为 HALF_OPEN,并清空计数器
- HALF_OPEN 时只允许单个试探请求;成功则恢复 CLOSED;失败则重置为 OPEN 并延长冷却时间
指数退避重试:动态计算延迟,尊重 signal 中止
退避不是简单 Math.pow(2, attempt),而要结合 jitter 防止雪崩,并确保每次 delay 都能被 signal 中断。
- 定义基础延迟 baseDelay = 100ms,最大重试次数 maxRetries = 3,jitter 范围 ±20%
- 第 n 次重试前,计算 delay = Math.min(baseDelay * Math.pow(2, n-1), maxDelay) * (0.8 + Math.random() * 0.4)
- 用 setTimeout + Promise.race 实现可取消延时:Promise.race([sleep(delay), new Promise((_, rej) => signal.addEventListener('abort', () => rej(new DOMException('Aborted', 'AbortError'))))])
- 仅当 signal 未中止时才发起下一次请求;一旦中止,立即抛出 AbortError,不再继续重试链
组合成网关函数:声明式配置 + 过程可控
最终暴露一个类似 gateway.request(options) 的函数,接收 URL、init、熔断 key、退避配置等,内部串联信号流、熔断判断、延时重试和请求执行。
- options 支持传入 abortSignal(用于外部干预)、circuitKey(用于熔断隔离)、retryConfig(含 maxRetries、baseDelay 等)
- 请求开始前先查熔断状态;若为 OPEN,直接 throw 新建的 CircuitBreakerOpenError
- 捕获网络错误、超时、非 2xx 响应,按策略分类:连接失败 / 服务端错误 / 客户端错误 → 只对前两类触发重试+熔断计数
- 每次请求完成(无论成功失败)都更新熔断窗口;成功时重置失败计数,失败时递增

















