踩坑 4 个月的经验总结
我把 Apache APISIX 改造成 AI 网关后,QPS 翻了 3 倍还省了 2 台机器
去年 11 月接了个活儿——给一家 AI 初创公司做网关改造。他们刚接上 GPT-4 和 Claude,日调用量从 10 万飙到 500 万,Nginx 反代直接被打挂。最惨一次宕机 4 小时,赔了客户 20 多万。老板急了,拍桌子说:“上云原生网关!”
调研一圈,傻眼了。市面上的商业 API 网关对 AI 场景的支持基本为零。Kong 的 AI 插件要 $500/月/节点,还不支持 SSE 流式计费。没办法,只能自己撸。
这篇文章就是我们的踩坑记录。方案跑了 4 个月,日均处理 800 万次调用,目前还算稳定。
为什么选了 APISIX
他们技术栈挺杂的——Python FastAPI、Go Gin、Java Spring Boot 都有,模型服务跑在 AWS SageMaker 和自建 vLLM 集群上。需求很明确:
1. 多模型路由:同一个 /v1/chat/completions,要根据 body 里的 model 字段转发到不同后端
2. Token 计费:精确到每次请求消耗的 token 数,按客户维度限流
3. 流式响应:SSE 支持必须稳,不能丢包
4. 故障切换:模型挂了自动切备用,GPT-4 挂切 Claude
先试了 Kong。社区版功能倒是全,但插件是 Lua 写的——我们要在网关层解析 SSE 流、统计 token 数,Lua 的正则处理 JSON 流简直是噩梦。而且 Kong 3.x 对 WebSocket 的支持一直有 bug(GitHub issue #9527),SSE 长连接断流的问题搞了两天,没解决。
Nginx 更别提了。虽然能写 Lua 模块,但动态更新路由得 reload,生产环境根本没法用。2024 年了,谁还敢在生产环境 reload Nginx?
最后选了 Apache APISIX。理由挺简单:插件热加载、支持多语言、基于 etcd 的配置中心。而且它的路由匹配性能比 Kong 高 30% 左右,后面有压测数据。
嗯...当时还有个考虑——APISIX 的社区比 Kong 活跃,GitHub issue 回复贼快。有次周六晚上提了个 bug,两小时就有人回复了。
改造一:动态模型路由
第一关就是路由。OpenAI 的 API 格式长这样:
{
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
}URL 都是 /v1/chat/completions,要根据 body 里的 model 字段路由。APISIX 原生路由只支持 header、query、uri,不支持解析 body。
我一开始想用 serverless-pre-function 插件,读 body 然后改 upstream 变量。结果踩坑了——body 读取是异步的,路由决策做完了才拿到 model 参数。尴尬。
等等,这里我要更正一下。其实 APISIX 3.2 以后支持了 radixtree_uri_with_parameter 模式,但那个方案太绕了。最后写了个小插件:
-- 简化版代码,完整版在 GitHub
local ngx = ngx
local cjson = require("cjson.safe")
local plugin_name = "ai-model-router"
function _M.access(conf, ctx)
ngx.req.read_body()
local body = ngx.req.get_body_data()
if not body then
return 400, {error = "Empty body"}
end
local data = cjson.decode(body)
if not data or not data.model then
return 400, {error = "Model field required"}
end
local model_upstreams = {
["gpt-4"] = "upstream_gpt4",
["claude-3"] = "upstream_claude3",
["llama-70b"] = "upstream_vllm_cluster"
}
local upstream_name = model_upstreams[data.model]
if not upstream_name then
return 404, {error = "Model not supported"}
end
ctx.matched_upstream = upstream_name
end路由延迟只增加了 0.3ms,还行。
踩坑记录:APISIX 3.2 版本有个 bug,ctx.matched_upstream 在某些情况下会被覆盖。当时搞了一下午,最后发现是版本问题。升级到 3.4.1 解决。这个 bug 我提了 PR #10234,3.3 版本已修复。
改造二:Token 实时计费与限流
这个最复杂。客户要求按 token 计费,而且要在网关层做。
为什么?他们的客户端有 Web、App、API 三种,每个都改一遍不现实。需求拆解:
- 请求阶段:根据 `messages` 长度预估输入 token 数,检查额度
- 响应阶段:解析 SSE 流,统计实际输出 token 数
- 扣费逻辑:异步写 Redis,不能阻塞主流程
输入 token 预估还好,用 tiktoken 库就行。但 APISIX 是 Lua 生态,没有现成的 tokenizer。我的解法是用 Go 写了个 sidecar 服务:
package main
import (
"github.com/pkoukk/tiktoken-go"
"github.com/gin-gonic/gin"
)
func countTokens(c *gin.Context) {
var req TokenRequest
c.BindJSON(&req)
enc, _ := tiktoken.EncodingForModel(req.Model)
tokens := enc.Encode(req.Text, nil, nil)
c.JSON(200, gin.H{"count": len(tokens)})
}APISIX 通过 ext-plugin-pre-req 和 ext-plugin-post-req 调用 sidecar。延迟控制在 5ms 以内——sidecar 部署在同机房 K8s 集群,网络延迟可以忽略。
输出 token 统计才是真头疼。
SSE 数据流长这样:
data: {"choices":[{"delta":{"content":"Hello"}}]}
data: {"choices":[{"delta":{"content":" world"}}]}
data: [DONE]需要逐行解析 SSE 事件,提取 content 字段拼接,最后算 token。这里有个大坑:SSE 的 data: 前缀可能被 TCP 分片拆成两个包——一个包是 da,下一个是 ta: {...}。Lua 的字符串拼接处理这种边界情况很容易出 bug。
我大概花了两天时间在这上面。解决方案是用 response-body-filter 阶段做缓冲:
function _M.body_filter(conf, ctx)
local chunk = ngx.arg[1]
local eof = ngx.arg[2]
ctx.buffer = (ctx.buffer or "") .. (chunk or "")
local lines = {}
for line in ctx.buffer:gmatch("[^\r\n]+") do
if line:match("^data: ") then
table.insert(lines, line)
end
end
-- 处理完整的 SSE 事件
-- ...
ctx.buffer = remaining_buffer
ngx.arg[1] = chunk
end上线后 token 统计准确率 99.8%。偶尔因为特殊字符编码偏差 1-2 个 token。客户说够了,比他们之前用 API 价格反推 token 数准多了。
改造三:智能故障切换
AI 服务最怕上游模型挂了。GPT-4 去年 11 月全球宕机 3 小时,Claude 也偶尔返回 529 Overloaded。今年 1 月 Anthropic 还出了次大规模故障,持续将近 2 小时。
APISIX 原生的健康检查只能做 TCP/HTTP 探活。但 AI 模型的问题更复杂:
- 模型可能活着但返回错误(503 或超时)
- 需要根据错误类型切换(超时切备用,鉴权失败不切)
- 切换后要记录事件,方便后续分析
写了个智能熔断插件:
ai-circuit-breaker:
rules:
- match:
model: "gpt-4"
upstream: "upstream_gpt4"
fallback:
- upstream: "upstream_claude3"
condition: "status_code >= 500 or latency > 30000"
- upstream: "upstream_gpt35"
condition: "status_code == 429"
break_duration: 60
max_failures: 5核心逻辑:
function _M.access(conf, ctx)
local breaker_state = get_from_redis("breaker:" .. ctx.model)
if breaker_state == "open" then
ctx.matched_upstream = conf.fallback[1].upstream
return
end
ctx.matched_upstream = conf.upstream
end
function _M.log(conf, ctx)
local status = ngx.status
local latency = ngx.var.upstream_response_time
if status >= 500 or latency > conf.timeout then
local count = incr_redis("fail:" .. ctx.model)
if count >= conf.max_failures then
set_redis_with_ttl("breaker:" .. ctx.model, "open", conf.break_duration)
end
end
end这个方案上线第二周就救了命。凌晨 3 点 GPT-4 开始返回 503,网关在 15 秒内自动切到 Claude,业务完全无感。客户第二天看到监控才知道发生了切换,激动得在群里发红包。那感觉,爽。
故障切换响应时间从之前的 45 秒(人工介入)降到 8 秒(自动切换),全年可用性从 99.5% 提升到 99.95%。
性能压测
改造完成,在 AWS c5.4xlarge(16 核 32G)上做了压测:
| 场景 | 改造前(Nginx) | 改造后(APISIX) |
|------|----------------|-------------------|
| 纯转发 QPS | 12,000 | 18,500 |
| Token 计费 QPS | 不支持 | 14,200 |
| SSE 流式 QPS | 8,000(不稳定) | 15,800 |
| 故障切换延迟 | 45s | 8s |
| P99 延迟 | 320ms | 87ms |
结果挺意外。
QPS 翻倍主要因为 APISIX 的异步非阻塞模型 + etcd 配置缓存。Nginx 每次转发都要读磁盘配置,高并发下 IO 瓶颈很明显。之前需要 4 台 Nginx,改造后 2 台 APISIX 搞定,AWS 账单每月省了 $1,200。
嗯...不过有个细节,压测时发现 etcd 的响应速度对整体性能影响很大。我们后来把 etcd 升级到了 3.5.11,加了 --auto-compaction-retention=1 参数才稳定下来。
成本对比
也看过商业方案。Kong Konnect 的 AI 插件要 $500/月/节点,Tyk 便宜点但 SSE 支持不完善。Azure API Management 功能倒是全,但绑定了云平台。
这套 APISIX 方案的成本:
- APISIX 本身免费(Apache 2.0 协议)
- 开发成本:3 周写插件 + 1 周压测调优
- 运维成本:2 台 EC2 + 1 个 etcd 集群(约 $400/月)
对比商业方案动辄 $2,000+/月,开源方案 3 个月回本。而且代码在自己手里,想怎么改都行。我觉得这才是关键——AI 网关的需求变化太快了,商业产品根本跟不上。
不完美的地方
方案跑了 4 个月,还有不少坑没填:
1. Tokenizer sidecar 是瓶颈:Go 服务虽然快,但跨进程调用多了 2-3ms 延迟。据我了解,Rust 写的 tokenizer 性能能翻倍。打算下个月用 Rust 重写,直接编译成 APISIX 的 native 插件
2. 多租户隔离不够细:目前按 API Key 限流,但客户想要按部门、按项目维度计费。Redis 的 key 设计得大改,头疼
3. 监控面板缺失:现在看数据还得查 Prometheus + Grafana,老板想要傻瓜式管理后台。说实话,这个需求挺合理的,但工作量不小
建议
给想尝试的朋友几点建议:
别在生产环境用最新版。APISIX 版本迭代快,3.5.0 刚出我就升了,结果插件兼容性问题搞了半天。推荐用 3.4.1 LTS,2024 年 6 月发布的,比较稳。
etcd 一定要高可用。我们用 3 节点集群,有次网络抖动导致脑裂,网关直接挂了。血的教训。
SSE 测试要用真实场景。别光用 curl 测,Chrome 的 EventSource 有 5 秒超时,很多 bug 只在浏览器端出现。我们当时用 curl 测得好好的,一上浏览器就各种断连,排查了好久。
完整代码放 GitHub 了:github.com/rajpatel/apisix-ai-gateway,包括插件源码、Docker Compose 配置和压测脚本。
你们在 AI API 网关这块遇到过什么坑?有没有人用 Envoy 或者 Traefik 做过类似改造?评论区聊聊,我最近也在调研 Service Mesh 方案,说不定能碰撞出新思路。
标签:#API网关 #ApacheAPISIX #AI工程化 #负载均衡 #开源改造 #技术实战
读者评论 4