重试次数和状态码范围,是 new-api 网关里最容易被误解的一对配置:RetryTimes 不是简单的"失败重试几次",它决定的是渠道档位能下探多深;而状态码范围决定哪些错误值得降级。搞清这两者的关系,才能配出"免费档逐级兜底、付费档最后接盘"的稳定链路,避免多配一个数字就多一次无效等待。
1. 重试的本质:次数是档位索引
先看一个反直觉的事实:new-api 的重试不是"同一个渠道再来一次",而是按优先级档位逐级换渠道。源码里 retry 序号直接当作档位索引(priorities[retry]),每次失败就跳到下一个 priority 档。
因此,一次性请求的实际尝试次数 = RetryTimes + 1(retry 从 0 数起)。假设你的渠道档位如下:
priority 110 → 渠道 A(免费)
priority 100 → 渠道 B(免费)
priority 90 → 渠道 C(免费)
priority 80 → 渠道 D(免费)
priority 50 → 渠道 E(中转)
priority 40 → 渠道 F(中转)
priority 30 → 渠道 G(付费兜底)
共 7 个 unique 档位,retry 序号 0..6 正好走完全部:
| RetryTimes | 能触达的最后一档 | 说明 |
|---|---|---|
| 5 | 40(渠道 F) | 到不了付费兜底 |
| 6 | 30(渠道 G) | 完整覆盖 7 档,刚好 |
| 7 | 30(渠道 G,重复) | 兜底渠道被多试一次,无增益 |
结论:RetryTimes 的最小值 = 档位数量 - 1。档位多了、RetryTimes 没跟上,付费兜底永远等不到;比档位数更大,只是让最后一档多被尝试一次,纯属浪费等待时间。
还有一个细节:retry 序号超出档位总数时会钳制到最低档(retry = len(priorities) - 1),所以 RetryTimes=7 的效果是"30 档被试两遍",而不是报错。
2. 决定哪些错误值得重试:状态码范围
new-api 的全局设置 AutomaticRetryStatusCodes 定义了"哪些状态码算可重试错误"。默认等价于:
100-199, 300-399, 401-407, 409-499, 500-503, 505-523, 525-599
永跳:504 / 524
容易被忽略的边界:
- 429 在范围内(409-499)——限流错误原生就会触发降级重试;
- 400 不在范围内——客户端参数类错误默认不重试;
- 504 / 524 无论设什么都不可重试——网关超时/上游断连,重试无意义。
判定时序:错误先可能经过渠道级 status_code_mapping 改写,再进入 shouldRetry 判断最终状态码是否落在上面的范围里。
3. 一个历史包袱:429 不需要映射
很多教程会让你在渠道高级设置里加状态码映射,把 429 映射成 503:
{"400": "500", "429": "503"}
429→503 这半段其实是历史包袱。早期资料认为 429 不可重试,所以需要先映射成 503 才能触发降级;但按上文的全局范围,429 本就在 409-499 里,原样透出照样降级。实测去掉映射后,日志里限流错误从"503"变回"429",降级链完全不受影响:
channel error (channel #8, status code: 429): inference exceeds tpm/rpm limit
channel error (channel #1, status code: 429): ...
SUCCESS: 7 use_channel: ["8","1","2","3","7"]
全挂时客户端看到的最终错误码也从 503 变回 429——对很多 SDK 来说,429 反而会触发它自己的退避逻辑,体验更合理。
4. 400 怎么进重试:全局比渠道映射更干净
400 不在默认重试范围,但真实场景里上游偶发 400(缺某个请求头、格式瞬时异常)是值得降级到下一档的。两个做法:
做法一:渠道级映射,把 400 改成 500:
{"400": "500"}
做法二:全局状态码范围直接加 400(推荐):
AutomaticRetryStatusCodes = 100-199,300-407,409-503,505-523,525-599
注意这里的等价合并:300-399 + 400-407 可写作 300-407,409-499 + 500-503 可写作 409-503,边界精确——408 依然不可重试(起点 409),504 依然不可重试(终点 503)。
推荐做法二的理由:重试判定收口到一处全局配置,渠道上不再残留状态码改写,渠道配置只剩 header/param 改写,心智负担小得多。
代价也要知道:400 进重试后,必现 400(参数写死错)的请求会拖着走完整条重试链、每档白试一遍。这类错误通常改一次就好,影响可接受。
5. 最佳实践配置
以"多档免费渠道 + 付费兜底"的典型架构为例:
渠道层(配置源统一维护):
- 档位按 priority 降序,数量 = RetryTimes + 1 或更少
- header_override / param_override 按需改写,不做状态码映射
全局 options:
RetryTimes = 档位数 - 1
AutomaticRetryStatusCodes = 100-199,300-407,409-503,505-523,525-599
改动纪律三条:
- options 直改数据库后必须重启 new-api 才加载(后台界面改则立即生效);
- 双实例部署时 options 不随渠道同步,两端要分别改、分别重启;
- 渠道配置改动后确认内存缓存已刷新(重启或走管理 API),SQL 直改不会自动同步缓存。
6. 验证与排错
验证降级链是否按预期工作,看日志两行就够:
channel error (channel #8, status code: 429): inference exceeds tpm/rpm limit
record consume log: ... use_channel:["8","1","2","3","7"]
- channel error 行确认"命中哪个渠道 + 上游原话";
- consume 日志的 use_channel 数组确认"这一请求实际经历了哪些档位";
- 想验证 400 可重试,构造一个会被上游 400 的请求(比如临时缺头),看它是否会继续下探而不是直接失败。
排查"模型频繁报错"时,永远先看 channel error 的渠道 id 和状态码,再决定动 RetryTimes 还是状态码范围——这两者分别是"能下探多深"和"什么值得下探"的问题,改错一个都会让链路失衡。
版权声明:如无特殊说明,文章均为本站原创,转载请注明出处
本文链接:https://tendcode.com/subject/article/newapi-retry-times-status-codes/
许可协议:署名-非商业性使用 4.0 国际许可协议