更新日期:2026-07-15
适用范围:需要通过统一 API 网关调用多个模型、并希望把 429 处理做成可验证工程能力的团队。本文不是任何供应商的吞吐量排行,也不提供跨平台“最快/最低价”结论。
先给结论
429 不是“客户端多等一会儿”就能解决的普通异常。它代表服务端当前拒绝继续接受请求;在 HTTP 语义里,服务端可以通过 Retry-After 告诉客户端何时再试,但不保证每一个 429 都带该字段。RFC 6585 定义了 429 Too Many Requests,而 RFC 9110 说明了 Retry-After 的两种表达形式。工程上,令牌桶、优先级队列和重试必须共用同一份请求预算与取消语义;把三者分散到 SDK、网关和业务层,会制造重复扣费、低优先级饿死和“已经超时仍在重试”的隐性故障。
先定义四个可测指标
不要只记录“429 次数”。一次压测至少记录以下字段:
| 指标 |
定义 |
为什么需要它 |
| 接受率 |
真正进入上游的请求 / 入站请求 |
区分网关本地限流与上游拒绝 |
| 429 比例 |
按模型、租户、优先级分组的 429 |
定位谁耗尽了预算 |
| 排队等待 |
入队到首次上游尝试的耗时 |
防止队列把可用请求拖成超时 |
| 端到端结果 |
成功、取消、超时、最终失败 |
防止重试掩盖用户体验 |
测试环境应记录网关版本、模型路由规则、并发数、超时、每租户配额、是否启用流式响应,以及测试开始/结束时间。缺少这些条件的“QPS”不能拿来比较。
三层控制必须共享状态
1. 令牌桶负责“能否立刻发”
令牌桶应按租户 + 上游模型 + 请求类别至少三个维度计费。仅按全局 key 限流会让一个批处理任务挤掉交互请求;只按租户限流又会忽略单一上游的容量。令牌不足时,不要直接把请求交给客户端循环重发,而是返回可解释的等待时间或放入受控队列。
2. 优先级队列负责“谁先获得下一个令牌”
优先级不能只是一列数字。建议至少分为:用户交互、在线 Agent 工具调用、可延迟批处理。每个优先级都应有最大排队时长;到期后必须取消或降级,而不是无限等待。还要设置老化规则:低优先级等待超过阈值后逐步提升,避免长期饥饿。
3. 重试负责“何时值得再试”
重试器应只接收仍未过期、尚未取消、且幂等性可确认的请求。优先遵守 Retry-After;没有该字段时,使用带抖动的指数退避,并受总尝试次数和总截止时间双重约束。流式生成已经输出部分内容时,默认不应自动重放,否则可能造成重复内容和重复计费。
可复现实验脚本的最小结构
下面是语言无关的控制流;它的重点不是具体数值,而是状态只在一个调度器内更新:
request -> deadline check -> priority queue
queue head -> acquire(tenant, model) token
no token: schedule at retry_after or next_refill
token: call upstream with idempotency key
upstream 429 -> release/record outcome -> retry only before deadline
upstream success -> record first-byte, total latency, usage
cancel/timeout -> remove queued work and block later retry
日志至少包含 request_id、tenant_id、model_route、priority、queue_ms、attempt、retry_after_ms、final_status。用这些字段才能回答“429 是哪个租户、哪条路由、哪种优先级造成的”。
常见失败模式
- 每层各自重试:SDK、网关和业务 worker 同时重试,放大流量。
- 令牌在请求完成后才扣除:突发流量会同时穿透桶,随后一起收到 429。
- 只看平均延迟:队列等待被平均值掩盖,少数交互请求已超时。
- 没有取消传播:浏览器关闭后,上游仍在生成并计费。
- 把 429 视为可忽略告警:持续 429 会让排队、重试和连接池全部失去稳定边界。
中转层怎样落地
像 top-api.cc 这类多模型接入层,更适合把路由、配额、预算告警和调用日志放在同一个控制面:业务侧只声明请求优先级和截止时间,网关负责选择上游并返回可审计结果。接入前先在测试租户中验证 429、取消、切换和费用归属,再将策略扩大到生产租户。
限制与下一步
不同供应商对 RPM、TPM、并发和流式计费的定义并不相同;本文的队列结构不能替代供应商文档或合同限额。上线后应按周复盘按租户/模型划分的 429、排队等待和最终失败,并把异常样本保留到可回放的审计日志中。
参考资料
- RFC 6585:429 Too Many Requests
- RFC 9110:Retry-After 字段语义
一次上线前演练的步骤
先为交互、后台和回填任务各创建一个测试租户,并给每类流量配置独立的并发与令牌预算。第一轮只发送低于限额的稳定流量,确认成功率、首包时间和 usage 字段可以关联到 request_id;第二轮逐步提高并发,直到本地桶开始拒绝;第三轮再提高到上游出现 429。三轮之间不要更换模型、提示词或超时,否则结果无法归因。
演练结束后,抽取十条“排队后成功”、十条“按 Retry-After 重试”、十条“截止时间到期取消”的记录。检查同一个 request_id 是否只有一次最终扣费、队列项是否在取消后被删除、低优先级是否没有无限等待。若无法回答这些问题,先修正日志和状态机,再讨论提升限额。
还应明确对调用方的返回契约:本地拒绝使用什么状态码、是否返回建议等待时间、是否区分租户配额和上游限额、流式连接中断如何标识。稳定的错误契约能让业务侧停止盲目重试,也让客服、财务和研发看到同一份事件解释。
复盘节奏
将以上指标按小时、租户和模型路由保留趋势图,并为突发 429、排队超时和重试放大设置独立告警。告警触发后先冻结新增重试,再核对限额配置和上游状态,避免排障动作本身继续放大负载。