← 返回资讯
苏晴
资深编辑
已审核

踩坑 4 个月的经验总结

去年 11 月接了个活儿——给一家 AI 初创公司做网关改造。他们刚接上 GPT-4 和 Claude,日调用量从 10 万飙到 500 万,Nginx 反代直接被打挂。最惨一次宕机 4 小时,赔了客户 20 多万。老板急了,拍桌子说:“上云原生网关!”

踩坑 4 个月的经验总结

踩坑 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 格式长这样:

JSON
{
 "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 模式,但那个方案太绕了。最后写了个小插件:

LUA
-- 简化版代码,完整版在 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 三种,每个都改一遍不现实。需求拆解:

输入 token 预估还好,用 tiktoken 库就行。但 APISIX 是 Lua 生态,没有现成的 tokenizer。我的解法是用 Go 写了个 sidecar 服务:

GO
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-reqext-plugin-post-req 调用 sidecar。延迟控制在 5ms 以内——sidecar 部署在同机房 K8s 集群,网络延迟可以忽略。

输出 token 统计才是真头疼。

SSE 数据流长这样:

CODE
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 阶段做缓冲:

LUA
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 模型的问题更复杂:

写了个智能熔断插件:

YAML
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

核心逻辑:

LUA
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 方案的成本:

对比商业方案动辄 $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工程化 #负载均衡 #开源改造 #技术实战

356
8922 阅读
4 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年06月27日 18:13

苏晴

资深编辑

科技媒体从业 8 年,曾就职于多家科技媒体。关注 AI 创业和投资赛道,采访过 50+ 位行业从业者。

读者评论 4

老李 1周前
有个小问题想请教,文中提到的那个方案在大规模场景下性能怎么样?
回复 点赞 (5)
运营小陈 1周前
转发到团队群了,大家都觉得有参考价值。
回复 点赞 (4)
数据分析师 昨天
数据引用很扎实,建议补充一下近三个月的最新数据。
回复 点赞 (9)
产品经理阿杰 4天前
从产品角度看,这个方向确实有机会,但商业化路径还需要验证。
回复 点赞 (15)