面试知识库
高 进阶

LLM网关与多供应商路由#

一句话答案#

LLM 网关是所有模型调用的统一出口:对上暴露一套 OpenAI 兼容接口,对下管多家供应商和自部署模型。它做四件核心的事——按供应商/模型的 RPM + TPM 双维限流(Redis Lua 原子扣两个桶,TPM 发前预估预扣、回来按真实 usage 回补)、熔断与降级(只对”对面坏了”计数,首 token 超时算故障,切到能力相当的备用模型)、重试与背压(可重试错误指数退避、遵守 Retry-After,自己限不住时向调用方回 429)、计量与成本归因(按上游返回的 usage 记账到租户/应用/模型)。

核心要点

单次调用的超时、重试、finish_reason 处理见 [LLM API调用与流式输出](/topics/llm-serving/LLM API调用与流式输出);租户级配额分档与预算止损见 多租户隔离与配额治理。本篇讲网关这一层内部的机制。

1. 为什么要统一网关#

业务直接调 SDK 时,问题会在规模上来之后集中出现:

问题没有网关时网关怎么做
供应商配额每个服务、每个副本各自调用,供应商看到的是 N 倍速率,429 频发全局共享一本限流账(Redis)
单点故障某家挂了 = 全站挂;每次调用都要等满超时才失败熔断快速失败 + 切备用模型
切换成本换模型要改代码、改配置、重新发版模型名映射到出口,改路由配置即可
成本不可见账单只按 API Key 汇总,说不清哪个业务花了多少每次调用按租户/应用/功能打标签记账
密钥与审计Key 散落在各服务Key 只在网关,业务用虚拟 Key
flowchart LR
    A[业务服务] -->|OpenAI 兼容请求 + 虚拟 Key| G[鉴权 / 租户配额]
    G --> R[路由:模型名 → 部署列表]
    R --> B{熔断器允许?}
    B -->|否| FB[下一个 fallback 部署]
    B -->|是| T[双令牌桶 RPM+TPM 预扣]
    T --> P[供应商适配 / 流式转发]
    P --> S[按真实 usage 结算 + 记账]

顺序有讲究:熔断检查和令牌桶都放在占用本地并发位之前。熔断器处于打开状态时,先占一个并发位再拒绝,等于把快速失败省下的资源又还了回去;等令牌时占着本地并发位也会饿死其他模型的请求。

2. 限流:RPM + TPM 双维令牌桶#

供应商的配额本来就是两维:每分钟请求数(RPM)和每分钟 token 数(TPM)。只限一维都会出问题——只限 RPM,一条 30K 输入的长请求和一条 500 token 的短请求同价,TPM 照样撞线;只限 TPM,大量短请求能打满 RPM。算法选型(令牌桶 vs 滑动窗口)见 限流方案设计,这里讲 LLM 场景特有的三点:

  1. 两个桶要原子地一起扣:要么都扣,要么都不扣。先扣了 RPM、发现 TPM 不够再退回,并发下那一格很容易漏账,表现是”桶没满却老在等”,不报错,很难查;
  2. TPM 要预估:发请求前不知道会消耗多少 token,只能估:输入 token(含工具 schema、system)+ 预计输出(max_tokens 或按历史均值取一个值);
  3. 回来再回补:响应的 usage 回来后按 预扣 − 实际 退还或补扣;真实消耗超过预估时允许桶扣成负数,后面的请求自然等待,账最终是平的。

结算脚本同理:先按时间补充,再把 reserved − actual 加回 TPM 桶,上限封顶在容量、下限不设(允许为负)。工程细节:

  • 桶的 key 按 供应商/模型(或部署)划分,和供应商配额的粒度一致;租户级配额是另一层,叠在前面;
  • Redis 不可用或脚本超时:退回进程内桶,限额按副本数分摊,同时打指标告警,不要让限流组件拖垮主链路;
  • 等待有上限:排队超过上限就向调用方返回 429 + Retry-After,或者按业务选择放行并记录;
  • 为什么要 Lua:读余量、判断、写入如果分成多条命令,并发下有竞态,Lua 在 Redis 里原子执行,见 Redis事务与Lua脚本。

3. 熔断:难点在”什么算故障”#

熔断器三态(closed → open → half-open)的通用原理见 熔断降级原理。LLM 场景的关键是只对”对面坏了”计数:

信号是否计入熔断理由
5xx、连接失败、连接重置计对面真的出问题了
首 token 超时(TTFT 超过阈值)计,并且真掐断连接供应商过载时常表现为”连接建立了但迟迟不出字”;不掐断只是换个地方等满总超时
429不计说明我们发得太快,对面是好的;计进去会把自己的限流放大成服务不可用,降速是限流器的事
总超时(流一直在吐只是长)不计长回答本来就慢,计进去会在长回答多时误熔断
4xx(参数错、上下文超长)不计请求本身的问题,换谁都一样

判定方式可以是”连续 N 次失败”或”滑动窗口内错误率超过阈值且样本数足够”,后者在流量大时更稳。熔断器按 供应商/模型 分别维护,一家的某个模型挂了不影响别的。半开探测遇到 429 时要特殊处理:既不能当成功清零(等于用一次限流把前面的故障记录洗掉),也不能什么都不做(状态会卡在半开),应该退回 open 状态、等下一次探测。

4. 降级与 fallback:切过去之前先确认”能用”#

  • fallback 链:主模型熔断或重试耗尽后,按配置的链切到备用部署(同模型的另一个区域/另一家托管 → 同档位的其他模型 → 更小的模型);
  • 能力门:备用模型要具备业务必需的能力。Agent 场景最关键的是能不能调用工具,其次是上下文长度、结构化输出。不支持 prompt caching 只是变贵变慢,不支持工具调用则等于切了不如不切。能力信息来自模型元数据表时要注意”查不到”和”明确不支持”是两回事,查不到应放行并告警,否则会把正在用的模型误判为不可用;
  • 流式中途失败不能透明切换:首 token 之后连接断开,用户已经看到半截回答,网关无法无缝换一家续写。fallback 只对首 token 之前的失败成立,之后只能向上报错,由应用决定重试整轮还是提示用户;
  • 切换的代价:换模型后前缀缓存全部失效(首轮更贵更慢),输出风格和格式可能变化,所以切换事件要打日志、带上”少了哪些能力”,并在评测里覆盖备用模型。

5. 重试与幂等#

  • 可重试:429(速率类)、5xx、连接错误、首 token 超时;不可重试:400(参数/上下文超限)、401/403、内容过滤。
  • 指数退避 + 抖动:sleep = min(cap, base * 2**attempt) * random.uniform(0.5, 1);响应里有 Retry-After 就以它为准。
  • 只在一层重试:SDK 自带重试(如 OpenAI Python SDK 默认会自动重试,次数以官方文档为准)、网关重试、业务重试叠在一起会变成乘法,3 层各 3 次就是 27 次请求。约定只在网关重试,SDK 设 max_retries=0;
  • 重试预算:限制重试请求占总请求的比例(例如一个时间窗内重试不超过一定比例),供应商故障时避免重试风暴把自己的配额也耗光;
  • 每次重试重新计时:首 token 预算按每次尝试单独计算,否则前几次失败的耗时会算到最后一次头上;每次尝试的超时也要收到本轮请求的总截止时间以内;
  • 幂等:LLM 调用天然不幂等,重试会重复计费。网关用调用方传入的 request_id / Idempotency-Key 在短时间窗口内去重(相同 key 返回同一结果或拒绝);已经开始流式输出的请求不重试;有副作用的工具调用在工具侧做幂等,见 [Function Calling与工具编排](/topics/ai-agent/Function Calling与工具编排)。

6. 429 与背压#

网关同时处在两个方向上:

  • 对上游(供应商):收到 429 时读 Retry-After 和剩余额度类响应头(如 OpenAI 的 x-ratelimit-remaining-tokens,各家头名不同),主动降低该出口的并发或拉开请求间隔(类似 AIMD:出错快速降、恢复慢慢升)。还要区分”速率超限”和”额度/余额用尽”——后者重试没有意义,应该直接告警并切出口;
  • 对下游(调用方):自己的令牌桶等待超过上限、或租户配额用完时,向调用方返回 429 + Retry-After,把压力推回去,而不是无限排队。队列有长度上限,超过就拒;交互式请求和批量任务分优先级,批量任务可以等、交互式请求优先。

7. 计量与成本归因#

  • 以上游 usage 为准:输入、输出、命中缓存的输入、推理 token 分别记,计价时按各自单价;不要用网关自己的 tokenizer 估算值记账(估算只用于预扣)。有的网关库在上游没返回 usage 时会自己估一份补上,计费场景要确认拿到的是上游那份;
  • 流式要开 usage 回传:OpenAI 兼容接口加 stream_options: {"include_usage": true},usage 在最后一帧;客户端中途断开时,网关要么继续把上游流读完拿到 usage,要么取消上游并按已转发内容估算,两种选择要写清楚;
  • 归因维度:租户、应用、功能点、用户、模型、供应商。调用方通过请求头或 metadata 传标签,网关写入异步日志(Kafka → ClickHouse 等),不阻塞主链路;
  • 价格表带版本:模型价格会变,按”模型名 + 生效时间”查价,历史账单不受新价格影响;
  • 告警与止损:按预算设软限告警和硬限拒绝,分档策略见 多租户隔离与配额治理;调用链路追踪见 Agent可观测性与质量保障。

8. 开源方案#

方案形态特点
LiteLLMPython SDK + Proxy 服务上百家供应商统一成 OpenAI 格式;Router 支持多部署负载均衡、fallback、重试、冷却、按部署的 RPM/TPM;Proxy 提供虚拟 Key、预算、花费统计;迭代很快(2026-09 仍在按周发版),升级前要回归
One API / New APIGo 服务 + 管理后台渠道(上游 Key)管理、令牌额度分发、OpenAI 兼容转发,国内使用多;One API 更新已明显放缓(仓库最近一次提交在 2026-01),New API 是在它基础上继续开发、目前仍活跃的分支
Higress / Kong 等 AI 网关基于通用 API 网关的插件在已有网关上加 AI 代理、token 限流、缓存等插件,适合已有网关体系的团队

选型判据:通用的部分用现成的,有业务口径的部分自己写。“哪个模型名对应哪个 base_url 和 Key”是通用问题,用 LiteLLM Router 这类组件即可;“什么算故障""TPM 预扣要不要算工具 schema""降级时必须保留哪些能力”带有业务口径,现成实现的默认值未必符合,需要自己定义甚至自研。

面试回答(2分钟版)

统一网关解决的是模型调用规模化之后的几个问题:多副本各自调用会让供应商看到 N 倍速率,一家挂了全站挂,换模型要发版,成本说不清谁花的。网关对上暴露 OpenAI 兼容接口,对下管多家供应商。限流上,供应商配额是 RPM 和 TPM 两维,我用 Redis Lua 同时扣两个桶,要么都扣要么都不扣;TPM 发之前按输入加工具 schema 加预计输出预扣,回来按真实 usage 回补,超了允许扣成负数。熔断的难点是什么算故障:5xx、连接失败、首 token 超时算,而且首 token 超时要真的关掉连接;429 不算,因为那是我们发太快,应该降速而不是熔断;总超时也不算,长回答本来就慢。熔断或重试耗尽后按 fallback 链切备用模型,切之前过能力门,Agent 场景至少要能调工具;流式出了首 token 之后断掉没法透明切换。重试只在网关一层做,指数退避加抖动,遵守 Retry-After,再加重试预算防风暴;幂等靠 request_id 去重。自己的桶等太久就给调用方回 429 加 Retry-After。计量按上游 usage 分输入、输出、缓存命中分别记,按租户和功能打标签。选型上一种常见取舍是:寻址、多部署负载均衡这类通用部分交给 LiteLLM Router 之类的现成组件,断路器和跨副本的 RPM+TPM 双令牌桶自己写,因为”什么算故障""预扣怎么算”这些判据带有业务口径。结合项目时可以讲:哪些用了现成组件、哪些自研、切换前用什么数据验证没有引入额外延迟或改写请求。

追问与易错

追问方向:

  • “为什么 RPM 和 TPM 必须在一个 Lua 脚本里扣?” → 分两步扣时,先扣 RPM 再发现 TPM 不够,需要退回 RPM,并发下这个”扣了又退”很容易漏账或多扣;一个脚本里先判断两个桶都够再一起扣,Redis 单线程执行保证原子。
  • “TPM 预估不准怎么办?” → 预估只用来发请求前占额度,响应回来按 usage 回补;估少了允许桶变负,后续请求自然等待,总账是对的。要避免的是预估恒为 0 之类的失效——那样预扣形同虚设、并发时照样撞供应商的 TPM 线。
  • “429 为什么不计入熔断?” → 429 表示对面健康、是我们发得太快;计进熔断会在流量高峰时把一个正常的供应商熔断掉,把”限流”放大成”不可用”。正确处理是降速和退避。
  • “首 token 超时和总超时为什么区别对待?” → 首 token 迟迟不来通常说明对面排队或过载,是故障信号;首 token 之后流一直在吐只是回答长,不该判故障。首 token 超时还要主动关连接,否则连接和资源仍被占着。
  • “流式输出到一半上游断了,网关能自动切备用模型吗?” → 不能透明切换,用户已经收到半截内容,换模型续写会出现不连贯;只能把错误传给上层,由应用决定整轮重试或提示用户。fallback 只对首 token 之前的失败有效。
  • “网关、SDK、业务都有重试会怎样?” → 次数相乘,一次故障放大成几十次请求,还会重复计费;约定只在一层(通常是网关)重试,SDK 的自动重试关掉。
  • “Redis 挂了限流怎么办?” → 退回进程内令牌桶,限额按副本数分摊,并通过指标告警;限流组件的故障不能让主链路不可用,但要知道这段时间的限额是不精确的。
  • “怎么把成本归到具体业务功能?” → 调用方在请求头或 metadata 里带租户、应用、功能标签,网关用上游返回的 usage 按版本化价格表计价,异步写入分析库,按标签聚合出报表。

易错点:

  • ❌ “用网关自己的 tokenizer 估算 token 记账” → 估算只用于预扣,记账必须用上游返回的 usage,否则和供应商账单对不上。
  • ❌ “所有错误都熔断、都重试” → 4xx 重试和熔断都没意义,429 应该降速而不是熔断。
  • ❌ “配了 fallback 就高可用了” → 备用模型可能不支持工具调用或上下文不够长,切过去照样失败;流式中途失败也接不住。