AI网关第8篇:AI 代理与负载均衡--多模型实例如何调度?

开头:转发那一站,藏着 AI 网关最"AI 味"的东西

上一篇结尾我留了个钩子:请求走完 rewrite、access 阶段后,要进入"转发"这一步------把请求真正发给上游模型。

在传统网关里,"转发"这一步基本就是"按负载均衡策略挑一台机器,发过去,等回来"。这件事做得很成熟,轮询、加权、最少连接,几行配置就搞定。

但在 AI 网关里,这一步突然变得一点都不简单 。因为你要调度的不是"一堆无差别的 Web 服务器",而是一堆昂贵、慢、状态不一、还会"看起来活着其实已经不行了"的模型实例。

这一篇,我就把"转发"这一站拆开讲:AI 网关怎么挑实例、怎么判断实例健康、实例挂了怎么办。难度 🟡,延续上一篇的机制拆解风格。


二、为什么传统负载均衡在这里失灵了

先讲清楚问题,才知道解法为什么长那样。传统负载均衡的假设,在 AI 场景下被三条现实击穿了。

差异一:连接是"流式长连接",不是"短请求快响应"

传统网关面对的是 REST API,请求进去、几十毫秒返回、连接关闭。负载均衡可以简单地"谁当前连接少就发给谁"。

而大模型几乎都是流式输出 (SSE),一个请求的连接会持续几秒到几十秒,token 一个一个往外吐。这时候"当前连接数"这个指标就失真了------一个慢实例可能连接数很少,但每个连接都拖着一堆长任务;一个快实例连接数很多,但吞吐其实更高。

用"连接数"去衡量 AI 实例的负载,基本是瞎指挥。

差异二:实例是"昂贵且不均"的

传统 Web 服务器,扩容是加一台"一样的机器",大家都差不多快。而模型实例不一样:

  • 不同实例可能跑在不同算力上(有的 GPU 好、有的差),吞吐和延迟能差出几倍;
  • 不同实例可能对应不同配额(有些是付费额度、有些是免费额度、有些有 RPM/TPM 上限);
  • 你甚至可能把不同供应商的同一个模型混在一个池子里(作为降级备胎)。

这决定了:AI 网关的负载均衡,不能平均主义,必须支持"按实例的能力权重来分配"。

差异三:"健康"的定义变了

传统健康检查是"能不能 ping 通 / 返回 200"。但对模型实例来说,"能返回 200"离"能用"还差很远:

  • 实例可能活着,但慢到不可接受(延迟从 2 秒飙到 20 秒);
  • 实例可能活着,但错误率飙升(因为并发打满、或内部降级);
  • 实例可能活着,但上下文窗口对不上(配置错了,长请求必然失败)。

所以 AI 网关的健康检查,不能只看"通不通",得看**"快不快、错不错、够不够"**。


三、多实例调度:从"平均分配"到"按能力分配"

讲完问题,讲解法。调度策略本身不算新,但 AI 场景下的用法变了。
#mermaid-svg-D5iI83p38cX8uQ0v{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-D5iI83p38cX8uQ0v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-D5iI83p38cX8uQ0v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-D5iI83p38cX8uQ0v .error-icon{fill:#552222;}#mermaid-svg-D5iI83p38cX8uQ0v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D5iI83p38cX8uQ0v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-D5iI83p38cX8uQ0v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D5iI83p38cX8uQ0v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D5iI83p38cX8uQ0v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-D5iI83p38cX8uQ0v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D5iI83p38cX8uQ0v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D5iI83p38cX8uQ0v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D5iI83p38cX8uQ0v .marker.cross{stroke:#333333;}#mermaid-svg-D5iI83p38cX8uQ0v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D5iI83p38cX8uQ0v p{margin:0;}#mermaid-svg-D5iI83p38cX8uQ0v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-D5iI83p38cX8uQ0v .cluster-label text{fill:#333;}#mermaid-svg-D5iI83p38cX8uQ0v .cluster-label span{color:#333;}#mermaid-svg-D5iI83p38cX8uQ0v .cluster-label span p{background-color:transparent;}#mermaid-svg-D5iI83p38cX8uQ0v .label text,#mermaid-svg-D5iI83p38cX8uQ0v span{fill:#333;color:#333;}#mermaid-svg-D5iI83p38cX8uQ0v .node rect,#mermaid-svg-D5iI83p38cX8uQ0v .node circle,#mermaid-svg-D5iI83p38cX8uQ0v .node ellipse,#mermaid-svg-D5iI83p38cX8uQ0v .node polygon,#mermaid-svg-D5iI83p38cX8uQ0v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D5iI83p38cX8uQ0v .rough-node .label text,#mermaid-svg-D5iI83p38cX8uQ0v .node .label text,#mermaid-svg-D5iI83p38cX8uQ0v .image-shape .label,#mermaid-svg-D5iI83p38cX8uQ0v .icon-shape .label{text-anchor:middle;}#mermaid-svg-D5iI83p38cX8uQ0v .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-D5iI83p38cX8uQ0v .rough-node .label,#mermaid-svg-D5iI83p38cX8uQ0v .node .label,#mermaid-svg-D5iI83p38cX8uQ0v .image-shape .label,#mermaid-svg-D5iI83p38cX8uQ0v .icon-shape .label{text-align:center;}#mermaid-svg-D5iI83p38cX8uQ0v .node.clickable{cursor:pointer;}#mermaid-svg-D5iI83p38cX8uQ0v .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-D5iI83p38cX8uQ0v .arrowheadPath{fill:#333333;}#mermaid-svg-D5iI83p38cX8uQ0v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-D5iI83p38cX8uQ0v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-D5iI83p38cX8uQ0v .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D5iI83p38cX8uQ0v .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-D5iI83p38cX8uQ0v .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D5iI83p38cX8uQ0v .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-D5iI83p38cX8uQ0v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-D5iI83p38cX8uQ0v .cluster text{fill:#333;}#mermaid-svg-D5iI83p38cX8uQ0v .cluster span{color:#333;}#mermaid-svg-D5iI83p38cX8uQ0v div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-D5iI83p38cX8uQ0v .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-D5iI83p38cX8uQ0v rect.text{fill:none;stroke-width:0;}#mermaid-svg-D5iI83p38cX8uQ0v .icon-shape,#mermaid-svg-D5iI83p38cX8uQ0v .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D5iI83p38cX8uQ0v .icon-shape p,#mermaid-svg-D5iI83p38cX8uQ0v .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-D5iI83p38cX8uQ0v .icon-shape .label rect,#mermaid-svg-D5iI83p38cX8uQ0v .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D5iI83p38cX8uQ0v .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-D5iI83p38cX8uQ0v .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-D5iI83p38cX8uQ0v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 健康
不健康/失败
成功
失败
请求进入转发阶段
实例池里有谁?
按权重挑一个实例
实例健康吗?
发送请求
切换下一个备选
结果如何?
流式透传响应

基础策略:轮询与加权轮询

最基础的是轮询 (round-robin)------大家轮流来。但这在差异二面前明显不够,于是有了加权轮询:给每个实例配一个权重,权重高的多分流量。

权重的设定,在 AI 场景里通常对应这些现实因素:

  • 算力强弱------GPU 好的实例,权重调高;
  • 配额高低------额度充足的实例,权重调高;
  • 成本高低------贵的实例,权重调低(省钱导向时)。

一个真实做法:把权重做成"可运行时调整"的。我们在线下发现某个实例明显更快,就临时调高它的权重、把流量往它身上倾斜,观察一段时间再决定是否固化。权重不是拍脑袋写死,而是根据观测动态校准的。

进阶策略:最少连接 + 语义修正

"最少连接"在传统网关是主力,但在 AI 场景需要修正:不要只看"当前连接数",要看"当前在途的 token 数"或"当前平均延迟"。

因为一个流式请求占用的是"持续的算力",而不是"一个瞬时连接"。一个实例即便连接数少,只要每个连接都是大并发、长流式,它已经饱和了。把"在途 token 数"纳入负载判断,才接近真实负载。


四、健康检查:从"通不通"到"快不快、错不错"

这是 AI 网关最值得讲的一环。健康检查分两种,我们两种都用:

主动探测:定时"敲门"

网关定时向每个实例发一个极轻量的探测请求(比如一个最小 token 的推理请求),看它能不能在规定时间内正常返回。

这里的关键是探测请求本身要够轻------你不能为了健康检查,每次都让实例真的生成一大段文本,那既费钱又费算力。一个 token 输出就够判断"它还活着、还能算"了。

被动观察:靠"真实流量"积累判断

主动探测只能回答"活没活着",回答不了"现在好不好用"。所以更关键的是被动观察------让每个真实请求都"顺手"上报自己的表现:

  • 这次请求的延迟是多少?
  • 这次请求成功还是失败?
  • 失败时错误码是什么?

把这些观测聚合起来,就能给每个实例"打健康分"。当某个实例的错误率超过阈值、或延迟持续高于阈值时,把它标记为"不健康",暂时从调度池里摘掉。

我们踩过的一个真实场景:某实例因为上游供应商内部降级,没有返回错误,而是返回了极慢的流式 (每 token 间隔拉长到秒级)。主动探测看它"活着",被动观察看它"慢得像乌龟"------最后是靠着延迟阈值的被动观察,才把它自动摘掉的。只做主动探测,这种"活着但废了"的实例永远发现不了。

摘除与恢复

不健康的实例被摘除后,不是永久封杀,而是进入观察期 :继续用少量探测流量试探它,如果表现恢复正常,再把它加回池子。"能摘也能回",避免一个抖动就让实例永久下线。


五、故障切换:主模型挂了,怎么兜住

健康检查的目的是"提前发现",但总有措手不及的时候------比如请求发到一半,实例直接崩了。这时候就要故障切换(fallback)。

故障切换的核心逻辑不复杂:主选实例失败 → 换下一个备选实例 → 再失败再换 → 全失败才报错。 但落到 AI 场景,有几个细节很要命。

细节一:切换要"带着语义"切

普通请求切换,重发一遍就行。但 AI 请求切换,你要保证切到新实例后,请求的语义还是一致的。

这意味着:请求体(提示词、历史、参数)要原样重发;但实例相关的部分(比如某个供应商特有的字段、模型名映射)要能对应替换。如果两个实例来自不同供应商、模型名不同,切换时还得做一层"模型名翻译",否则备胎实例压根不认这个请求。

细节二:切换要"赶在流式开始前"

最尴尬的故障是:响应已经开始流式输出,吐了半截,实例挂了。 这时候你怎么办?

  • 如果直接断掉重发,用户已经看到的那半截就作废了,体验很差;
  • 如果硬着头皮把半截传下去,又是不完整的结果。

我们的做法是:尽量把 fallback 的判定放在流式开始之前(连接建立、首 token 之前)。首 token 之后,除非彻底断连,否则不轻易切------宁可把"已经流出来的部分"交给上层应用去判断,也不在流中间粗暴重发。

细节三:全失败要有"明确的失败"

如果所有备选实例都试完了还是失败,网关必须返回一个明确的错误(而不是把请求静默吞掉,或者返回一个半截响应)。这个错误要能告诉调用方"发生了什么",否则调用方会一脸懵。


六、踩坑与边界

照例收尾上坑。

坑一:健康检查阈值拍脑袋,导致"误杀"或"漏杀"

延迟阈值、错误率阈值如果定得太激进,网络一抖动就把实例全摘了,反而放大故障;定得太保守,坏实例一直留在池里拖后腿。

教训:阈值要基于历史基线来定,而不是拍脑袋。 先观测一段时间每个实例的正常延迟分布,再定"几倍于基线算异常",比直接写死一个绝对数字靠谱。

坑二:fallback 的"语义丢失",是最隐蔽的坑

前面说了,切换要带着语义切。但实际最容易翻车的是参数不兼容:主实例支持某个参数,备胎实例不支持,切换过去后请求直接报 400。这种坑只在"切换发生时"才暴露,平时测不出来。

教训:凡是配置了 fallback 的,一定要做一次"强制切到备胎"的演练,不能等真出事了才第一次切。

坑三:多实例 + 流式 + 观测,监控成本翻倍

多实例调度让系统更强壮,但观测维度也跟着翻倍------每个实例的延迟、错误率、在途 token、健康分,都要有地方看。如果只做了调度、没做观测,出问题就是"两眼一抹黑"。

边界:单实例场景,别上这套

最后呼应一下第 4 篇说过的"过度设计"。如果你背后就一个模型实例、没备胎、也没多供应商,那这套多实例调度+健康检查+fallback 就是纯负担。先问自己"有没有多个可调度对象",再决定要不要上这套机制。


结语

这一篇,我把"转发"这一站讲透了:为什么传统负载均衡在 AI 场景失灵(长连接、昂贵不均、健康定义变)、多实例怎么调度(按能力加权)、健康检查怎么做(主动探测+被动观察)、故障怎么切换(带语义切、赶在流式前、明确失败)。

到这里,阶段二(架构与改造)就完整了:从选型(第 5 篇)→ 数据面/控制面分离(第 6 篇)→ 插件体系(第 7 篇)→ 多实例调度(第 8 篇),你已经对"一个开源网关要做成 AI 网关,架构层要动哪些地方"有了完整认知。

但这些都是"架构层"的故事。从下一篇开始,我们要下探到源码级(🔴) :那个数据面到底是跑在什么运行时上、请求进来后底层经历了什么、配置又是怎么做到秒级生效的。下一篇,就从最底层的运行时处理模型讲起。

相关推荐
东离与糖宝1 小时前
Kafka流算湖一体化重磅发布,AI实时数据链路迎来大变革
人工智能
释厄6231 小时前
胶球实验本体论解码··力直接构成物质悖论·切割 A让B=B
人工智能
不会就选b1 小时前
算法日常・每日刷题--<动态规划>29
数据结构·算法·leetcode
IvorySQL1 小时前
IvorySQL 校园行走进山东大学:从 PostgreSQL 到 AI,连接技术实践与职业成长
数据库·人工智能·postgresql
垆边人似月.1 小时前
最少的元素个数使和为 target
数据结构·c++·算法
炽橙超真云1 小时前
2D 工程图纸如何自动转成可编辑 3D 模型?拆解读图与建体两条技术路径(DXF/DWG/PDF 到 STEP)
算法·3d·工业智能·工业ai
传人once1 小时前
从 PPT 到可交互原型:用 AI 做小程序原型的实践 | 深度实践版
人工智能·powerpoint·交互
夕除1 小时前
redis--dict rehash
算法·哈希算法
元岳数字人小元1 小时前
数字人私有化,如何搭建全链路内网推理体系
运维·人工智能·开源·人机交互·交互