在 LLM 场景中,为什么要用指数退避而不是固定间隔重试?如果多个 Agent 实例同时触发重试,会出现什么问题,怎么解决?
这个问题问到点上了,固定间隔重试在生产里基本是“好心办坏事”。我从两个维度讲,最后串起来。
🔁 为什么是指数退避,不是固定间隔¶
试想一个场景:你的 LLM API 返回了 429 Too Many Requests,这意味着服务端在主动限流。
- 固定间隔重试,比如每隔 1 秒重试一次。 如果失败是由短暂的过载引起的,1 秒后服务端可能还没缓过来,你的重试又撞在枪口上,再次 429。 更糟糕的是,如果所有请求都按同一节奏重试,就会形成“同步脉冲”:失败 → 等1秒 → 同时重试 → 再次过载 → 失败 → 等1秒…… 像个永动机,服务根本恢复不了。

-
时间轴一走,所有重试挤在一起。
-
指数退避,比如等待序列:1s → 2s → 4s → 8s(通常还会封顶)。 它的核心思想不是“等得越久越好”,而是给服务端一个恢复的窗口,同时拉大重试间隔。 如果第一次失败是因为瞬时过载,退避 1~2 秒可能就够了;如果第二次还失败,说明问题更严重,需要更长时间恢复,4 秒甚至 8 秒的等待能有效避开流量高峰。 用这种方式,客户端的重试节奏被自然拉开,不会集中在同一时刻。
所以指数退避本质上是 “背压反馈”:失败越多,你施加的负载越小。
🧨 多个 Agent 同时重试会发生什么?¶
这是经典的惊群效应(Thundering Herd Problem)。
假设你有 20 个 Agent 实例,都在调用同一个 LLM API。某刻,API 返回批量限流,20 个实例几乎在同一毫秒内收到失败响应。
如果它们都使用完全相同的指数退避算法(且没有抖动),计算出的等待时间会一模一样。于是:
-
T+1s:20 个实例同时重试 → 瞬间 20 QPS → 再次集体 429
-
T+2s:它们又同时重试 → 又一次集体失败
-
T+4s:继续同步……
就算是指数退避,也只能延后这个脉冲,而不能消除它。服务端看到的就不是平滑的请求,而是一阵一阵的“流量浪涌”,很可能再次触发限流阈值,陷入死循环。
💡 解法:加抖动,并且让抖动有效¶
最直接的手段就是在等待时间中加入随机抖动(Jitter)。
比如原本计算等待 4 秒,我们不精确等 4 秒,而是从 0 到 4 秒之间随机取一个值(即 Full Jitter)。 这样 20 个实例的等待时间会散落在 0~4s 这个区间内,重试请求自然错开,从“齐步走”变成“散步”。
这个随机化带来的效果,我用一个示意对比:
除了抖动,还有几个进阶办法:
-
退避上限 + 最大延迟:不能无限指数增长,否则抖动范围过大也可能等太久。设个
max_delay(比如 60s),让等待可控。 -
退避策略差异化:不同 Agent 可以使用略微不同的
base_delay,比如通过环境变量或实例 ID 设置一个微小偏移,进一步打散。 -
熔断机制:连续失败达到阈值,直接短路一段时间,不再重试,避免给服务端雪上加霜。
-
带退避令牌桶:客户端自己控制总重试 QPS,用令牌桶限制整体重试速率。
实际在生产里,我一般在每个服务实例初始化时,用一个固定种子生成随机数生成器,既保证了可复现调试,又让各实例间的随机序列不同,天然错开。
🎯 聊到这里你可能也感受到了,指数退避解决的是“个体如何合理等待”的问题,而抖动解决的是“群体如何不自相残杀”的问题。API 调用不是单机程序,设计重试策略时必须把“别人也在重试”这个前提考虑进去,否则架构上的优雅就被并发现实的残酷打个粉碎。