防止代理重试风暴:退避、抖动、预算与安全恢复

代理网关将请求洪峰分散为受控重试流

重试可以修复短暂网络故障、出口过载或目标服务的临时错误,也可能把小故障放大为严重事故。当大量工作进程同时重试时,代理、目标站点、队列和连接池会在容量最紧张时承受更多流量。

正确做法不是完全取消重试,而是让重试具备选择性、延迟、上限、可观察性和操作安全性。

先判断错误是否适合重试

临时连接重置、受控超时、部分网关错误,以及明确的限流或服务不可用响应,通常可以考虑重试。认证失败、无效请求、策略拒绝和确定性的配置错误一般需要修复,而不是重复发送。

应为每个客户端和目标维护可重试条件白名单,避免“所有非 200 响应都重试”这类粗放规则。

只保留一个重试责任层

应用代码、HTTP 客户端、任务执行器、代理 SDK、队列消费者和编排系统都可能自带重试。如果每层都尝试三次,一个任务可能产生数十个下游请求。

指定一个层级负责重试,关闭或严格限制其他层的隐藏重试,并把所有尝试计入同一个预算。上线前应计算最坏情况下的请求放大倍数。

使用带上限的指数退避与随机抖动

立即重试或固定间隔会让工作进程同步。指数退避会逐次延长等待时间,随机抖动则让进程不在同一时刻醒来。

实用策略应包含:针对临时网络故障的小基础延迟、针对限流的较长延迟、零到当前上限之间的随机抖动、单次等待上限、最大尝试次数、最大重试总时长,以及对有效 Retry-After 指令的尊重。

设置共享重试预算

仅限制次数仍不够。如果所有失败请求都重试三次,总负载仍可能变成三倍。可在滚动时间窗口内限制“重试请求数占初始请求数”的比例。

预算耗尽后,应快速失败、暂停受影响路由,或把任务放入有界延迟队列。不能在未核对容量的情况下,把无限失败流量转移到另一个代理地区。

应把重试预算与代理超时预算结合,确保 DNS、连接、认证、TLS、响应、退避和排队都在任务截止时间内。

保证操作幂等

重试会修改状态的请求前,要判断第一次尝试是否可能已经成功。目标支持时使用幂等键,否则设计可以识别重复结果的对账步骤。

GET 通常适合重试,但不一定成本低。POST、购买、表单提交或账户修改需要更强的重复保护。超时不代表目标端没有完成处理。

限制并发与队列

如果无界队列持续释放新任务,退避也无法保护服务。应设置按目标和按代理的并发上限,限制队列深度并监控队列等待时间,在内存和连接池耗尽前拒绝或延后多余任务。

同时检查代理连接池指南,避免故障路由长期占用连接或破坏隔离边界。

谨慎设计熔断器

熔断器用于暂时停止明显故障的路由,为恢复留出时间,而不是永久拉黑。应定义失败阈值、冷却时间、半开探测速率和恢复标准。

熔断范围应对应真正故障的维度,例如代理端点、出口地区、目标、协议或凭据组。全局熔断可能误伤健康流量。

监控请求放大

分别统计初始请求和重试。重点指标包括重试比例、每个完成任务的尝试次数、重试后成功率、预算耗尽、队列等待时间、熔断状态、p95 完成时间和每个有效结果的成本。

日志可以使用稳定操作 ID 和尝试序号,但不得记录代理密码或认证请求头。如果重试比例上升而有效结果下降,应停止增加尝试并检查底层路由。

上线检查清单

  • 建立可重试错误与状态白名单。
  • 指定唯一重试责任层。
  • 使用带上限的指数退避和完整随机抖动。
  • 尊重有效的服务端重试时间。
  • 限制次数、总时长、并发和队列深度。
  • 防止状态修改操作产生重复结果。
  • 按故障路由设置熔断范围。
  • 监控重试放大和成本。
  • 上线前模拟局部故障。

合规与安全

不得用重试绕过目标站点的限流、拒绝信号或访问控制。只测试已获授权的系统,保持约定的请求压力,最小化数据收集,并在目标要求暂停时停止发送。

常见问题

已有指数退避,为什么还需要抖动?

没有抖动时,同一时间失败的进程仍可能在相同的指数间隔重试。随机化可以把请求分散到不同时间。

所有代理错误都应该切换出口重试吗?

不应该。认证、策略、请求格式和持续性目标错误通常需要诊断。盲目轮换出口只会增加成本和目标负载。

最佳重试次数是多少?

没有统一答案。应根据任务截止时间、错误类型、操作安全性、流量规模和下游容量确定,并通过受控故障测试验证。