云计算百科
云计算领域专业知识百科平台

从 429 到熔断:AI 聚合网关的故障切换与并发治理

/* 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 倍。如果上游正在熔断边缘,重试就是压垮它的最后一根稻草。

所以并发必须分层控制:

  • 全局信号量:限制网关同时在途的请求总数,超出直接排队或快速失败。
  • 单上游预算:每个供应商独立维护并发上限与 TPM 令牌桶,互不挤占。
  • 重试预算:每个请求分配固定重试次数(建议 2 次),配合带抖动的指数退避,避免所有失败请求在同一时刻集体重试。
  • 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 应用的稳定性问题,本质上是把「调用别人的服务」这件事工程化:

  • 先分类再重试,只有限流、超时、5xx 值得消耗重试预算;
  • 熔断粒度要细到「上游 + 模型」,否则故障会横向传染;
  • 并发治理必须分层,重试会放大并发,全局信号量要包住整个重试过程;
  • 流式请求只有首 token 前可以安全切换;
  • 指标是切换策略的输入,没有观测就没有有效的降级。
  • 如果不想自己维护这一整套路由、熔断和配额体系,也可以直接选用现成的聚合网关。这类平台把多上游的凭证池、熔断策略和重试预算收敛在统一入口,业务侧只面对一个 OpenAI 兼容接口。例如 LINK TOKENS(www.linktokenview.com)就是这种一站式 Token 服务平台,它把多供应商的故障切换、并发限流和用量统计做在网关层,开发者在做多模型冗余时改一个 base_url 就能接入,不必在业务代码里维护复杂的供应商分支。当然,阈值定多少、策略怎么配,仍然要结合自己的流量特征去压测和调优——这部分没有银弹,只有观测数据。


    AI 一站式服务平台

    公司网站:www.linktokenview.com

    link tokens, view the future

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 从 429 到熔断:AI 聚合网关的故障切换与并发治理
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!