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

MaaS 平台架构落地:从单租户网关到多租户模型路由的演进

1. 问题背景:模型网关成了新的性能瓶颈

2025 年我们团队负责某云厂商内部 MaaS(Model as a Service)平台的架构升级。平台对外提供统一的模型推理 API,内部对接了 12 个开源大模型(Qwen2.5-72B、Llama-3.1-70B、DeepSeek-V3 等),日均推理请求约 380 万次,峰值 QPS 约 4200。

业务侧反馈的核心痛点是:P95 延迟从 1.2 秒恶化到 3.8 秒,模型路由错误率从 0.3% 上升到 2.1%。客户投诉集中在两个场景:一是高峰期部分请求被错误路由到负载过高的模型实例;二是新上线的模型无法灰度,只能全量切换,导致线上事故频发。

我们最初的设计是一个单租户的 Nginx + Lua 网关,按模型名做静态哈希路由。这个方案在日均 50 万请求时运行良好,但流量增长 7 倍后,问题集中爆发。

2. 根因分析:静态路由与共享网关的双重失效

排查了三天,我们把问题收敛到三个根因:

  • 静态哈希无法感知实例健康度:Nginx 按模型名哈希到固定实例,某实例显存被打满后,请求仍然持续涌入,导致排队和超时。
  • 单租户网关缺乏配额隔离:一个业务方的大批量任务(如离线批量推理)会占满连接池,挤占在线推理请求的带宽。
  • 模型版本管理缺失:新模型上线只能通过修改 Nginx 配置并 reload 完成,无法按租户或按流量比例灰度。

一个典型的故障日志片段如下:

2025-06-18 14:32:07 ERROR gateway: upstream timed out (110: Connection timed out)
while connecting to upstream, client: 10.20.3.15, request: "POST /v1/models/qwen2.5-72b/completions"
upstream: "http://10.0.8.21:8001", host: "maas.internal.example.com"
2025-06-18 14:32:08 ERROR gateway: no live upstreams while connecting to upstream, client: 10.20.3.15

10.0.8.21 这台 A100 实例的显存利用率已达 98%,但网关仍然把新请求路由过去,直到连接超时。

3. 方案对比与选型:自研路由层 vs 引入 API 网关

我们评估了两个方向:

维度方案 A:自研 Go 路由服务方案 B:基于 Apache APISIX 3.9 扩展
路由灵活性 完全可控,可深度定制模型亲和性策略 依赖插件机制,复杂策略需写 Lua 插件
多租户配额 需自行实现令牌桶与配额存储 内置 consumer 级限流限速插件
灰度发布 需自行实现权重路由与版本管理 内置 traffic-split 插件,支持按权重分流
运维成本 需维护独立服务与存储 复用已有 APISIX 集群,运维成本低
团队熟悉度 Go 栈熟悉,但需 2-3 周开发 已有 APISIX 运维经验,1 周可上线

最终我们选择方案 B:基于 APISIX 3.9 扩展。核心考量是:团队已有 APISIX 生产运维经验,且 traffic-split 和 consumer 限流插件能直接覆盖灰度与配额两个核心诉求,不需要从零开发。自研方案虽然灵活,但在 3 周内完成并保证稳定性风险过高。

4. 实操步骤:多租户模型路由的落地实现

整体架构调整为:客户端请求 → APISIX 网关(路由 + 限流 + 灰度)→ 模型路由服务(实例健康度感知)→ 推理实例池。

第一步,在 APISIX 中配置多租户 consumer 与配额限流。以下是关键配置片段(APISIX 3.9,etcd 3.5):

consumers:
– username: tenant_alpha
plugins:
key-auth:
key: alpha-secret-key
limit-count:
count: 2000 # 每分钟 2000 次
time_window: 60
rejected_code: 429
policy: local
limit-conn:
conn: 50 # 单租户最大并发连接
burst: 10
default_conn_delay: 0.1

第二步,通过 traffic-split 插件实现新模型灰度。以 Qwen2.5-72B 从 v1 升级到 v2 为例,先切 10% 流量验证:

{
"uri": "/v1/models/qwen2.5-72b/completions",
"plugins": {
"traffic-split": {
"rules": [
{
"weighted_upstreams": [
{ "upstream": { "type": "roundrobin", "nodes": { "10.0.8.21:8001": 1 } }, "weight": 10 },
{ "upstream": { "type": "roundrobin", "nodes": { "10.0.9.14:8001": 1 } }, "weight": 90 }
]
}
]
}
}
}

第三步,模型路由服务每 5 秒从 Prometheus 拉取各实例的显存利用率、GPU 利用率、队列深度三个指标,计算综合健康分,低于阈值(如显存利用率 > 92%)的实例从 APISIX 上游节点中临时摘除。核心逻辑如下(Go 1.22):

// health_score 计算实例健康分,低于 60 分则摘除
func healthScore(gpuUtil, memUtil, queueDepth float64) float64 {
score := 100.0
if memUtil > 92 { score -= 40 } // 显存超 92% 扣 40 分
if gpuUtil > 95 { score -= 30 } // GPU 打满扣 30 分
if queueDepth > 8 { score -= 20 } // 队列深度超 8 扣 20 分
return score
}

路由服务通过 APISIX Admin API 动态调整 upstream 节点权重,实现秒级摘除与恢复。

5. 踩坑与排错:三个真实问题

坑一:traffic-split 权重不生效。灰度上线第一天,10% 的权重实际变成了 50%。排查发现 APISIX 3.9 的 traffic-split 在多个 rule 同时存在时会叠加权重,而不是按比例分配。解决方法是只保留一个 rule,内部用 weighted_upstreams 数组表达多版本权重。

坑二:限流插件误伤在线推理。limit-count 的 policy 设为 local 时,每个 APISIX 节点独立计数,3 节点集群导致实际配额放大 3 倍。改为 policy: redis 并指定 Redis 7.0 集群后,配额统计才准确。

坑三:健康检查摘除抖动。路由服务每 5 秒拉取指标,某实例显存利用率在 91%-93% 之间波动时,会被反复摘除和恢复,导致连接频繁重建。最终加入 30 秒的冷却时间(hysteresis),连续两次低于阈值才摘除,连续两次高于恢复阈值才重新上线。

6. 验证数据与效果

上线两周后,我们对比了改造前后的核心指标:

指标改造前改造后变化
P95 推理延迟 3.8 秒 1.4 秒 下降 63%
路由错误率 2.1% 0.2% 下降 90%
GPU 平均利用率 61% 78% 提升 17 个百分点
新模型灰度周期 2 天(全量切换) 4 小时(10%→50%→100%) 缩短 80%

峰值 QPS 4200 时,网关层新增延迟约 8ms,可忽略不计。多租户配额生效后,离线批量任务不再挤占在线推理,在线请求的超时率从 1.8% 降到 0.15%。

7. 复盘:什么场景该用,什么场景不该用

这套方案适合模型数量多(>5 个)、租户多(>3 个)、流量波动大的 MaaS 平台。APISIX 的插件生态让我们用较低成本解决了灰度、限流、健康路由三个核心问题。

但也要说清楚边界:如果只有 1-2 个模型、日均请求低于 10 万,直接用 Nginx 静态路由加简单的健康检查就够,引入 APISIX 和路由服务反而增加运维复杂度。另外,如果团队对 OpenResty/Lua 不熟悉,且没有现成的 APISIX 运维能力,自研 Go 路由服务(方案 A)在长期可维护性上可能更优——APISIX 的插件调试在复杂策略下确实比较痛苦。

最后一点:健康路由依赖 Prometheus 指标的实时性,指标采集延迟超过 10 秒时,摘除动作会滞后,建议在推理实例侧同时做一层本地限流兜底,避免网关摘除不及时导致实例 OOM。

赞(0)
未经允许不得转载:网硕互联帮助中心 » MaaS 平台架构落地:从单租户网关到多租户模型路由的演进
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!