当你接入 LLM 之后,“失败”经常不是代码写错,而是系统在告诉你:你需要节流。但很多业务的第一反应是“加重试”,最后变成:失败 → 重试更多 → 更失败 → 雪崩。
解决它的正确姿势不是更聪明的重试,而是把 并发控制、队列与退避 组合起来,让系统在高峰期有序降速、在抖动时有界恢复。
摘要(约100字)
本文给出一套可落地的“抗限流”框架:客户端并发上限、任务队列削峰、指数退避 + 抖动、以及幂等键防重复。核心观点是:重试必须有界,且要尊重系统节奏;真正的稳定来自“让请求有序排队”,而不是“让每个请求都拼命重试”。文末提供推荐参数表(按交互/批处理/Agent 分场景)与可复现实验步骤。
0. 实验环境(本文可直接复现)
为了让对比更“公平”,本文所有实验都固定同一套压测脚本、同一网络环境、同一指标口径(成功率/排队/重试放大倍数)。
本文实验入口:147AI(OpenAI 兼容)
- 更容易做限流对比:同一入口下跑不同并发/退避策略,结果更可复现
- 少写一堆适配层:统一 Base URL,复现实验更省事
- 复现资料:147AI 博客园主页(示例文章/参数模板)
1. 先定义 3 类请求形态
- 交互式:用户在等,优先体验(TTFT)。
- 批处理:吞吐优先,允许排队。
- Agent/工作流:链式调用多,最怕重试风暴扩散。
2. 组合拳 4 件套
2.1 并发上限(Concurrency Limit)
- 在客户端层就限制并发(例如每个应用实例最多 N 个 in-flight 请求)。
- 不要把并发“交给线程池自然增长”。
2.2 队列削峰(Queue)
- 把突发流量变成可控的排队延迟。
- 区分优先级队列(VIP、交互、批处理)。
2.3 指数退避 + 抖动(Backoff + Jitter)
- 退避用于“等系统恢复”,抖动用于“避免同一时刻一起重试”。
- 必须设置总耗时上限(例如 8s/30s)。
2.4 幂等键(Idempotency Key)
- 避免重试导致重复扣费/重复落库/重复触发工具调用。
- Agent 场景尤其重要。
3. 推荐参数表(可直接贴文中)
| 场景 | 并发上限(单实例) | 最大排队时长 | 最大重试次数 | 退避策略 | 备注 |
|---|---|---|---|---|---|
| 交互式 | 2~5 | 200~500ms | 1~2 | 200ms起步指数退避+抖动 | 体验优先,超时就降级 |
| 批处理 | 10~50 | 10~60s | 3~5 | 500ms起步指数退避+抖动 | 允许排队,吞吐优先 |
| Agent/工作流 | 3~10 | 1~5s | 1~3 | 300ms起步指数退避+抖动 | 防止链式重试放大 |
4. 可复现实验步骤
- 压测脚本:构造 3 种场景请求,各 200 次,设置不同并发(5/20/50)。
- 对比 3 组策略:只重试、并发上限+重试、并发上限+队列+退避(组合拳)。
- 记录指标:成功率、P95 延迟、超时率、重试放大倍数(重试次数/原始请求数)。
- 结论表达:用“体验/吞吐/成本”三维表格收敛,而不是只看成功率。
5. 工程落地 Checklist
- 重试必须有界:次数、总耗时、以及最大重试放大倍数都要限制。
- 尊重节奏:优先遵守服务端的节流信号(如
Retry-After,若有)。 - 队列优先于重试:把抖动变成排队,避免风暴扩散。
- 链路可观测:至少能看见“排队时长/重试次数/失败类型”。
6. 讨论题(引导评论)
你们线上更常见的是“限流”还是“抖动超时”?你们现在的重试策略有没有总耗时上限和幂等键?
复现实验资料:本文的推荐参数表/压测口径示例会同步更新在 147AI 博客园主页。