因axios/fetch无内置重试与熔断机制,无法应对服务抖动;需组合指数退避重试与断路器(如resilience4j或gobreaker+backoff),且须确保断路器统计单次请求而非重试后结果。

为什么直接用 axios 或 fetch 无法满足高可靠异步请求?
因为它们本身不带重试策略、不感知下游稳定性、也不记录失败历史。当后端服务短暂抖动(如 503、超时、连接拒绝),裸调用会立刻失败,而真实场景需要:失败后延迟重试(且间隔递增),连续失败到阈值就主动熔断,避免雪崩。这不是“加个 retry 选项”能解决的——axios-retry 不支持指数退避参数定制,更不联动断路器状态。
resilience4j 是目前最轻量可控的 Java 网关方案
它把“重试”和“断路器”拆成独立组件,可组合、可监控、不侵入业务逻辑。关键不是堆功能,而是控制粒度细:
-
RetryConfig支持自定义maxAttempts、waitDuration、intervalFunction(用IntervalFunction.ofExponentialBackoff实现指数退避) -
CircuitBreakerConfig可设failureRateThreshold(如 50%)、waitDurationInOpenState(熔断后静默期)、slidingWindowSize(滑动窗口大小决定统计精度) - 两者通过
Decorators组合:Decorators.ofSupplier(xxx).withRetry(retry).withCircuitBreaker(cb).decorate()
注意:不要把重试和断路器套在同一个 Supplier 外层再嵌套——这会导致熔断器统计的是“重试后的最终结果”,而非“单次请求成败”,失去意义。正确做法是先重试、再由断路器包裹整个重试块。
Go 里用 gobreaker + backoff 组合要避开三个坑
gobreaker 本身不带重试,必须自己集成;而 backoff 库默认重试不感知断路器状态。常见错误写法是:
for i := 0; i < maxRetries; i++ {
if cb.Allow() {
resp, err := http.Do(req)
if err == nil {
cb.Success()
return resp, nil
}
cb.Failure()
time.Sleep(backoff.NextBackOff())
}
}问题在于:cb.Allow() 检查的是“当前是否允许发起请求”,但 time.Sleep 后没再检查——可能刚睡醒熔断器已跳闸。正确做法是每次循环开头都调 cb.Allow(),且失败后立即调 cb.Failure(),成功才调 cb.Success()。另外,backoff.ExponentialBackOff 的 InitialInterval 和 MaxInterval 必须显式设置,否则默认为 1s/60s,不适合高频短时服务。
Node.js 场景下别迷信 async-retry + opossum
async-retry 的 factor 参数名易误导——它不是“乘数”,而是“底数”,即间隔按 initial * factor^attempt 增长;而 opossum 的断路器默认只统计 reject,不捕获超时或网络异常(需手动包装 Promise.race 加 setTimeout)。实操建议:
- 用
retry({ factor: 2, minTimeout: 100, maxTimeout: 5000 })控制退避曲线 - 断路器创建时传
{ errorThresholdPercentage: 40, timeout: 10000 },并确保所有请求都走circuit.fire(promise)而非直调 - 务必监听
circuit.on('open')和'halfOpen'事件做日志,否则熔断触发后你根本不知道
真正的难点不在拼凑组件,而在“失败判定边界”——HTTP 500 要重试,503 要熔断,429 要降频,这些状态码必须在重试前就被路由到不同策略分支,而不是全扔给同一套退避逻辑扛。

















