多模型路由与故障恢复:重试、熔断和降级的边界

5 分钟阅读 阅读量
目录

把多个模型接进同一个网关,只是完成了统一入口。真正影响可用性的,是请求该去哪里,以及上游出错之后网关该怎么办。

如果路由只是「挑一个 URL」,遇到 429、超时或渠道故障时,业务仍要自己承担不确定性。反过来,如果网关不加区分地重试、切换和降级,也可能把一次故障放大成重复扣费、重复输出或全局雪崩。

本文从通用架构角度讨论模型路由和故障恢复,并用 ai-gateway 的设计作为一个落地参照。

先区分逻辑模型和实际渠道

业务通常希望请求一个稳定的名字,例如 chat-default,而不是把 vendor-x/model-y 写进每个服务。这就需要区分两个概念:

  • 逻辑模型:业务侧请求的名称,代表一组约定好的能力和策略;
  • 渠道(channel):一次真实的上游调用配置,包含供应商、凭据、模型映射和运行状态。
text
1
2
3
4
逻辑模型:chat-default
    ├── 渠道 A:供应商甲 / model-1 / 优先级高
    ├── 渠道 B:供应商乙 / model-2 / 优先级低
    └── 渠道 C:兼容接口 / model-3 / 备用

这个映射让业务和供应商解耦,但逻辑模型不能只是一张别名表。备用渠道是否具备相同上下文长度、工具调用、图像输入和输出格式?成本是否在可接受范围?这些都决定降级是不是语义上安全。

路由通常分成两步

把「哪些渠道可用」和「可用渠道里选哪个」分开,能让策略更清楚:

  1. 候选过滤:移除被禁用、没有目标模型映射、已熔断、处于冷却或容量不足的渠道;
  2. 候选排序/选择:按优先级、权重、连接数、成本或一致性等策略选择一个渠道。

常见策略并不存在通用的最佳答案:

策略 更适合的目标 需要留意的地方
优先级 主渠道优先,备用渠道兜底 主渠道恢复后流量可能瞬间回切
加权随机 按比例分散流量 权重不等于实时容量,仍要结合限流
最少连接 避免请求集中到忙碌渠道 长短请求差异和流式连接会影响判断
一致性 Hash 同一租户或键尽量命中同一渠道 节点变化会导致映射迁移,且可能造成热点
成本优先 降低平均推理成本 价格最低不代表能力、延迟或可靠性满足要求

真实路由往往先做资格过滤,再应用一种主要策略,并保留健康状态和业务规则。不要把各种策略都做成一个复杂的加权公式,却没有办法解释某次请求为什么去了某个渠道。

重试:预算和错误分类比次数更重要

「失败就重试三次」看起来简单,却没有回答几个关键问题:哪些错误可重试?每次等多久?是否已经超过用户可接受的等待时间?是否会重复产生副作用?

一个更稳妥的策略至少要考虑:

  • 错误分类:连接错误、超时、429 或部分 5xx 可能是瞬时问题;鉴权失败、模型不存在和参数错误通常不适合原样重试。具体状态码仍要看供应商语义。
  • 总 deadline:每次尝试共享一个总时间预算,不能每次重试都重新获得完整超时。
  • 退避与抖动:间隔逐渐增大并加入随机抖动,避免大量请求同时重试。
  • 尝试上限:换渠道重试也消耗上游配额与时间,必须有明确上限。

简单伪代码:

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
deadline = now + request_budget
for candidate in candidates:
    if now >= deadline:
        stop
    remaining = deadline - now
    result = invoke(candidate, timeout=remaining)
    if success:
        return result
    if not retryable(result.error):
        stop
    wait(backoff_with_jitter(attempt), at_most=remaining)

这比单纯设置 maxRetries = 3 更能表达真实约束:重试是在剩余预算内尝试恢复,而不是无限延长一次请求。

流式请求有一条重要边界

非流式请求失败后,客户端还没有看到响应内容,网关有时可以尝试另一个渠道。但流式响应一旦开始向客户端输出,局面就不同了:客户端已经收到一部分内容,网关再从头请求一次会造成重复、拼接错误,甚至两段回答互相矛盾。

因此常见的策略是:

text
1
2
首个响应内容发给客户端之前:可以按规则重试或切换
首个响应内容发给客户端之后:不再透明重试,转为结束流并报告错误

这要求网关跟踪响应是否已经开始向外发送,而不只是上游 TCP 连接是否成功。还要分别考虑连接超时、首字节超时和流式空闲超时:请求已经持续输出时,不能因为一个很长的总时长限制而误杀正常生成。

如果上游协议支持续传或响应 ID,可能有更复杂的恢复方案;但在一般的文本生成接口中,不能假设流式请求可以从断点继续。

熔断和冷却解决的是不同时间尺度的问题

重试处理的是当前这一次请求;熔断处理的是一段时间内持续失败的上游。当某渠道连续失败时,继续把新请求发给它只会浪费延迟预算,也可能加剧故障。

典型熔断器有三种状态:

text
1
2
3
CLOSED(正常)──失败达到阈值──→ OPEN(暂时拒绝调用)
     ↑                             │
     └── HALF-OPEN(放少量探测请求)┘

多实例场景还要考虑状态在哪里维护。本地熔断器实现简单,但每个实例看到的失败历史不同;共享 Redis 冷却窗口可以让实例更快达成一致,却引入了共享状态依赖。也可以让本地熔断器负责快速保护,让共享冷却信息负责实例间协调。选择要匹配系统规模和对一致性的要求。

熔断范围也很重要。按整个供应商熔断,隔离面大但可能误伤该供应商上的健康模型;按渠道或模型粒度隔离更精细,却需要更多状态和配置。

降级不是“换个模型继续”,而是能力契约

当主模型不可用时,备用模型可以提高可用性,但要检查它是否满足当前请求的最低能力要求:

  • 是否支持流式输出、工具调用或多模态输入?
  • 最大上下文和输出长度是否足够?
  • 输出格式是否与调用方预期兼容?
  • 费用和延迟是否在可接受范围?

所以降级链最好表达的是「可接受的替代关系」,而不只是按名称排序的模型列表。对于强依赖特定能力的请求,正确行为可能是明确失败,而不是悄悄换到一个不能完成任务的模型。

在 ai-gateway 中,路由策略、换渠道重试、模型降级和渠道熔断是协同工作的;项目也明确避免在流式内容已经开始输出后透明重试。这是具体实现选择,其他系统仍需要根据上游协议、业务容忍度和预算重新评估。

小结

设计模型路由和故障恢复时,先把这些边界讲清楚:

  • 逻辑模型提供稳定的业务入口,渠道代表真实上游能力;
  • 先过滤不可用候选,再按目标策略选渠道;
  • 重试要分类错误、共享 deadline,并使用退避和上限;
  • 流式响应开始对外输出后,通常不能再透明重试;
  • 熔断保护持续失败的上游,降级则必须满足能力契约。

高可用不等于「多试几次」。更好的目标是:在有限预算内恢复可恢复的失败,对不可恢复的失败尽早、明确地停止。

项目地址:liku-yu/ai-gateway。

相关阅读:

最后更新于 2026-10-04