/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
从 429 到熔断:AI 聚合网关的故障切换与并发治理
一、为什么单供应商架构在生产环境撑不住
把 LLM 接口接到线上之后,很多团队会很快遇到同一组问题:白天高峰期偶发 429、夜里批量任务把 TPM 打满、某个模型突然返回 503、一次区域抖动让首 token 延迟从 300ms 涨到 8s。这些现象和传统微服务故障有一个本质区别——你无法通过扩容来解决。上游是别人的集群,你能调的只有自己的调用姿势。
具体来说,AI 接口的故障有四个特点:
- 限流是常态而不是异常。 TPM/RPM 配额按账号维度计算,几个大客户同时跑批量任务就会互相挤占,429 出现的概率远高于传统 API。
- 长尾延迟极重。 P99 常常是 P50 的 5~10 倍,流式接口的首 token 延迟(TTFT)波动更明显。
- 故障没有预警。 区域抖动、模型版本灰度、算力调度都不会提前通知。
- 错误语义容易被混淆。 rate_limit_exceeded(可退避重试)和 insufficient_quota(重试一万次也没用)是两回事,如果混在一个 catch 分支里处理,只会白白浪费并发预算。
聚合层的价值正在这里:它不是简单地多接几家 API,而是把可用性从一个靠祈祷的问题,变成可以工程化设计的系统问题。下面按「故障分类 → 切换组件 → 并发治理 → 可观测」的顺序拆开讲。
二、第一步是给故障分类,而不是无脑重试
不同故障的处置策略完全不同。一个常见的错误做法是「任何非 200 都重试 3 次」,结果是在注定失败的请求上耗尽整个并发池。
| 故障类型 | 典型信号 | 可否重试 | 处置策略 | | — | — | — | — | | 限流 | 429 / rate_limit | 可,需退避 | 换 key、换上游、排队 | | 超时 | ReadTimeout | 可(幂等前提下) | 换节点、缩短 max_tokens | | 服务端错误 | 500 / 502 / 503 | 可 | 指数退避 + 熔断 | | 配额耗尽 | insufficient_quota | 同 key 不可 | 切备用凭证或上游 | | 模型不可用 | 404 / model_not_found | 不可 | 路由到等价模型 | | 内容拦截 | content_filter | 不可 | 直接返回,不消耗重试预算 |
规则很简单:只有前三类消耗重试预算,其余立即走降级分支。这一条能把无效重试砍掉一大半,也是最容易被忽略的收益。
三、故障切换的四个核心组件
3.1 熔断器:别让重试变成雪崩
最朴素的做法是「失败就换下一个上游」,但当一个供应商整体故障时,这会把你所有流量轮流砸向每一个节点。熔断器的三态模型(Closed / Open / Half-Open)是必需的:滑动窗口统计最近 N 次调用的失败率,超过阈值就打开,冷却期后放少量探测请求进去,成功则恢复。
关键是熔断粒度要细:按「上游 + 模型」维度,而不是按上游整体。某家供应商的 A 模型挂了,不代表 B 模型也挂了,粗粒度熔断会误伤大量可用容量。
3.2 路由:从「一个请求」到「一组候选」
路由层要同时考虑:
- 能力匹配:是否支持 function calling、vision、上下文长度
- 实时健康度:熔断状态、近 1 分钟失败率
- 成本权重:便宜优先,但要在延迟可接受区间内
- 就近接入:降低 RTT
- 负载水位:当前在途请求数与配额余量
一个可落地的加权评分可以写成:
score = 0.35 * 健康度 + 0.25 * (1 – 归一化延迟)
+ 0.25 * 成本优势 + 0.15 * 剩余配额
权重不必追求最优,只要保证熔断中的上游权重归零,切换就能正常工作。
3.3 并发治理:三层闸门
这是最容易被忽略、也最容易出事的环节。故障切换本身会放大并发:一次用户请求重试 2 次,瞬时并发就是原来的 3 倍。如果上游正在熔断边缘,重试就是压垮它的最后一根稻草。
所以并发必须分层控制:
3.4 流式响应的特殊处理
SSE 流式输出下,失败重试要非常谨慎:
- 首 token 尚未吐出时失败 → 可以安全切换上游重试
- 已经向下游写入部分内容后失败 → 重试会导致重复文本,只能标记断流,由前端兜底
实践建议是设置一个首字节超时(TTFB timeout),比如 3 秒内没收到第一个 chunk 就判定该上游不可用并切换。超过这个点用户已经感知到卡顿,切换的成本远低于继续等待。
四、一次完整的调用长什么样
把上面的组件串起来,核心逻辑大致如下(Python 伪代码,省略流式与指标上报):
async def call_with_failover(req, candidates, max_retry=2):
last_err = None
for upstream in route(candidates): # 已按 score 降序排序
if breaker[upstream].is_open():
continue
for attempt in range(max_retry + 1):
try:
async with sem_global, sem_upstream[upstream]:
resp = await upstream.chat(req, timeout=TTFB_TIMEOUT)
breaker[upstream].record_success()
return resp
except RateLimit as e:
await sleep(backoff(attempt, jitter=True))
breaker[upstream].record_failure(e)
last_err = e # 同一上游退避重试
except (Timeout, ServerError) as e:
breaker[upstream].record_failure(e)
last_err = e
break # 换下一个上游
except QuotaExhausted as e:
breaker[upstream].open_now() # 直接熔断,不重试
last_err = e
break
raise AllUpstreamsFailed(last_err)
几个细节值得注意:
- sem_global 必须包住整个重试过程,否则并发治理形同虚设
- 限流(429)在同一个上游内退避重试更划算,因为换上游可能触发另一家的限流
- 配额耗尽立即熔断该凭证,不要浪费任何一次重试
- 所有分支都要记录指标,否则你无法判断「切换变多了」是上游退化还是自己代码有 bug
五、可观测性决定故障切换的上限
没有指标,故障切换就只是碰运气。至少要采集:
- 按「上游 + 模型」维度的成功率、P50/P95/P99、TTFT
- 熔断器状态变化事件(打开与恢复的时间点)
- 重试次数分布——重试率突然上升往往是上游退化的最早信号
- 并发水位与排队时长,用于判断闸门是否设置过紧
跑一次故障演练也很有必要:人为让某个上游返回 429 或超时,观察流量是否平滑迁移、下游 P99 是否只上升有限幅度。没演练过的降级链路,等于没有降级链路。
六、总结
AI 应用的稳定性问题,本质上是把「调用别人的服务」这件事工程化:
如果不想自己维护这一整套路由、熔断和配额体系,也可以直接选用现成的聚合网关。这类平台把多上游的凭证池、熔断策略和重试预算收敛在统一入口,业务侧只面对一个 OpenAI 兼容接口。例如 LINK TOKENS(www.linktokenview.com)就是这种一站式 Token 服务平台,它把多供应商的故障切换、并发限流和用量统计做在网关层,开发者在做多模型冗余时改一个 base_url 就能接入,不必在业务代码里维护复杂的供应商分支。当然,阈值定多少、策略怎么配,仍然要结合自己的流量特征去压测和调优——这部分没有银弹,只有观测数据。
AI 一站式服务平台
公司网站:www.linktokenview.com
link tokens, view the future
网硕互联帮助中心



评论前必须登录!
注册