news 2026/9/12 4:31:25

AI大模型网关升级实战:部门费用明细与限流配额设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI大模型网关升级实战:部门费用明细与限流配额设计

过去半年,我一直在折腾一件事:给公司内部的AI大模型网关做一次服务升级,核心就两个功能——部门费用明细和限流配额。说起来简单,真正落地才发现,这两个功能背后牵扯的是企业内部AI治理的整个体系。这篇博文就把整个升级过程、设计思路、踩坑经历都摊开讲讲,适合正在做AI平台、模型接入层、或者准备在公司内部统一管理大模型调用的朋友参考,尤其是那些眼看着部门AI账单越来越乱、线上算力快被某个业务线打爆的团队。

先交代一下背景。我们公司今年AI应用增长很快,研发、市场、客服、运营都在接大模型API,一开始是各搞各的,有人用云厂商的key,有人自己部署了开源模型,还有人直接拿个人账号在测试。结果就是月底对账对不上,多个模型的调用量也没人说得清,线上并发一高,重要业务的请求就被顶掉了。这次网关升级的目的,就是把所有大模型调用统一收口,做到“谁用了、用了多少、花了多少钱、该不该让ta用”全部有据可查。下面按设计和落地的顺序,把核心方案和代码逻辑完整拆解出来。

1. 企业内部AI调用失控?网关就是来兜底的

1.1 没有网关的几个月,账目和算力都失控了

在没有统一网关之前,我们踩过两个非常典型的坑。第一个是费用失控。有一个团队用主账号的API key接了一个长文本批量处理任务,每天凌晨跑几万条数据,用的是价格比较高的pro模型,月底账单直接翻了三倍。问题是这个key是公共的,好几个项目都在用,财务找过来的时候根本说不清这钱是谁花的、花在哪了。

第二个是算力挤兑。另一个团队用开源模型做了个实时客服助手,本来是好事,但某天活动流量进来,所有请求全部打到同一个推理服务上,直接把GPU显存打满,导致另一个核心业务线的接口超时率飙到40%。当时我们的架构里根本没有租户隔离和优先级的概念,所有请求一视同仁,出问题只能靠运维临时重启服务。

这两个问题反映出来的本质是:企业内部一旦多个部门同时接入AI能力,就必须要有一个统一的入口来管理“身份、权限、计量、配额”。没有这个入口,业务跑得越快,后面欠的账就越多。

1.2 网关不只是路由代理,它更像门禁闸机

很多同学一听到“网关”,第一反应就是反向代理、转发请求。但AI大模型网关和普通的API网关有很大区别。普通网关关注的是URL路由、认证、限流,而AI网关必须额外关注token计量、模型路由、成本核算、流式响应透传。用个生活化的类比:普通网关是小区大门,只管让不让车进;AI网关是办公楼里的门禁闸机,刷卡才能进,刷了卡系统知道是谁进的、进了哪层楼、待了多久、这次访问消耗了多少访客额度。

这次升级我们把网关的职责拆成了四层:

  • 路由层:根据业务方传入的模型名称和配置,决定把请求转发到云厂商API、私有化部署的模型还是内部其他网关服务。
  • 计量层:统计每个请求的输入token、输出token、响应时间、费用估算,并把明细写入流水表。
  • 配额层:按部门、按应用、按时间窗口控制调用量和费用上限,超限自动拦截或降级。
  • 审计层:保存完整的请求日志和调用链信息,方便事后排查问题和对账。

这四个职责缺一不可。尤其是计量和配额,如果不和“部门”这个维度绑定,那收口就失去了意义,后面所有报表和治理都是空中楼阁。

2. 部门费用明细:从一笔糊涂账到分部门账单

2.1 费用归集维度设计:别只记一个总数

我们最开始做费用统计时,犯过一个特别低级的错误:只按“模型名”记了每天的调用次数和token量。结果领导问“这个月市场部花了多少钱”,我们答不上来,因为调用记录里根本没有部门字段。后来重构时,我们把费用归集的维度拆成了四个必选维度和三个可选维度,必选维度是所有流水记录都必须带上的:

  • 部门(dept_id):费用最终归属,必须从应用解析出来,不允许请求方自己传什么就记什么,避免伪造。
  • 应用(app_id):每个部门下可能有多个应用,比如市场部有内容生成应用、社媒监测应用,分开记录才能定位到具体业务的成本。
  • 模型(model):不同模型价格差异很大,必须单独记录。
  • 接口类型(api_type):chat、embedding、rerank这些接口计费方式不同,分开存。

可选维度包括用户ID、请求来源IP、业务标签(比如活动ID)。这些字段在不影响性能的前提下尽量都记上,后面做成本分析时会非常有用。比如我们后来发现某个部门周末的调用量比工作日还高,顺着用户ID一查,发现是有人在用公司key跑个人脚本,从源头堵住了滥用。

2.2 从请求到账单的完整链路

费用明细不能靠事后从模型服务商账单里反推,那样永远对不上。正确的做法是在请求链路上实时计量,把每一笔调用都登记成一条不可修改的流水。大致链路是:

  1. 客户端请求到达网关,携带应用标识和密钥。
  2. 网关完成鉴权,解析出部门ID、应用ID、目标模型。
  3. 网关向模型服务发起转发请求。对于普通同步请求,模型返回的结果里通常会带usage字段(input_tokens和output_tokens)。
  4. 网关根据模型计价表计算本次调用的估算费用。
  5. 把调用流水写入存储(MySQL或ClickHouse),关联部门、应用、模型、token数和费用。
  6. 定时任务按天/月聚合流水,生成部门费用报表。

看起来不复杂,但有一个环节特别容易出问题,就是流式响应(SSE)的token统计。如果模型接口是流式返回,很多模型服务商不会把usage放在每个chunk里,只在最后一段返回。如果网关只是简单透传而不解析最后的结束帧,这笔调用的token就是0,费用明细自然就漏了。这个问题我们上线第一周就遇到了,后面细说。

2.3 计价模型与流式token统计的坑

大模型的计费通常是输入和输出分开计价,输入token便宜、输出token贵,部分模型还有缓存命中token,价格更低。一个简单的计价公式:

费用 = 输入token数 × 输入单价 + 输出token数 × 输出单价

以我们内部一个pro模型为例,输入每百万token约20元,输出每百万token约60元。一个请求如果输入3000 token、输出1000 token,费用大约是0.06 + 0.06 = 0.12元。单个请求看起来不贵,但一天几百万次调用,费用就非常可观了。

流式统计的关键在于:网关必须识别SSE流的结束标记。以OpenAI兼容接口为例,返回流最后会有一个data: [DONE],而在这之前,会有一帧data: {"choices":[...],"usage":{...}}携带usage信息。我们当时的处理方式是:

  • 把SSE流按行解析,逐行透传给客户端。
  • 当遇到包含"usage"字段的帧时,解析并保存到请求上下文。
  • 当遇到[DONE]时,确定本次请求结束,使用已保存的usage计算费用。
  • 如果服务商实现不规范,没有返回usage,就退化用“估算模式”:累加所有delta中的content字符数,按经验比例估算token(中文字符约0.6 token/字,英文约1.3 token/词,仅供兜底)。

实际上,后来我们干脆在网关侧把SSE解析做成了标准组件,不管是接云厂商还是私有化模型,都能正确取到usage。这个组件是整个费用明细功能最核心的模块。

2.4 数据落库:流水表一定要设计成不可修改

费用流水表是审计和账单的基础,必须设计成只追加、不可修改、不可删除。我们在MySQL里建了一张call_ledger表,核心字段如下:

CREATE TABLE `call_ledger` ( `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `request_id` VARCHAR(64) NOT NULL COMMENT '全局请求ID', `dept_id` BIGINT NOT NULL COMMENT '部门ID', `app_id` BIGINT NOT NULL COMMENT '应用ID', `model_name` VARCHAR(64) NOT NULL COMMENT '模型名称', `api_type` VARCHAR(16) NOT NULL DEFAULT 'chat' COMMENT '接口类型', `input_tokens` INT UNSIGNED NOT NULL DEFAULT 0, `output_tokens` INT UNSIGNED NOT NULL DEFAULT 0, `cost` DECIMAL(10, 6) NOT NULL DEFAULT 0 COMMENT '估算费用', `latency_ms` INT UNSIGNED NOT NULL DEFAULT 0, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY `idx_dept_created` (`dept_id`, `created_at`), KEY `idx_app_created` (`app_id`, `created_at`), KEY `idx_request_id` (`request_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这张表的特点就是宽、索引多、不带更新操作。所有费用统计都基于这张表做聚合,业务上谁也不许改历史数据。如果要订正,只能新插入一条冲正记录,保证流水完整可审计。

报表查询可以用SQL直接聚合,也可以定时把流水同步到ClickHouse,用更灵活的方式做多维分析。数据量小时MySQL就够用,数据量大了建议加一层OLAP存储。

3. 限流配额:把稀缺算力公平分给每个部门

3.1 先把概念分开:QPS限流和额度配额不是一回事

刚开始做限流时,我们以为就是简单的“每秒最多X个请求”,后来发现完全不够。大模型调用有两个核心限制维度:

  • 速率限制(QPS/并发):防止瞬间流量打爆推理服务。
  • 额度限制(token/费用):防止某个部门把月度预算提前烧光。

这两件事必须分开配置。举个例子:市场部做内容生成,单个任务会并发调用100次,但每个请求量不大,总量可控;研发部的测试任务可能QPS不高,但每个请求都是万字长文,token消耗是前者的几十倍。如果只限QPS,研发部一个下午就能把整个月的预算跑完。

维度控制目标典型配置项超出后的处理
速率限制保护后端推理服务每秒最大请求数、最大并发数请求被拦截,返回429
额度限制保护部门预算每日/每月最多消耗token或费用请求被拦截,或自动降级到低成本模型

所以我们的配额配置拆成了两部分:一部分挂在部门上,用于费用和token的额度管控;另一部分挂在应用上,用于QPS和并发限制。两者独立生效,任何一个超出都会触发保护。

3.2 限流算法选型:滑动窗口、令牌桶到底选哪个

限流算法有很多种,固定窗口、滑动窗口、令牌桶、漏桶。我在这次升级里把常见算法都对比了一遍,最后结论是:QPS控制用滑动窗口,额度控制用Redis计数器,允许突发的场景再叠加令牌桶。

  • 固定窗口:实现最简单,但两个窗口交界处会出现流量尖峰。比如限制每分钟100次,第一个59.9秒内放了100次、最后一个0.1秒又放100次,前后一秒钟打了200次进来。
  • 滑动窗口:比固定窗口平滑很多,对时间边界的处理更合理。用Redis ZSET实现,精度高,但每个请求都要记录一个时间戳,内存开销稍大。
  • 令牌桶:允许一定程度突发,适合处理大模型调用中“某个请求内部需要多次重试”的场景。限制的是平均速率,同时允许瞬间冲一下。
  • 漏桶:强制恒定速率,对AI这种单请求耗时不稳定的场景不太适合,容易拖慢整体响应。

我们的生产实现是:QPS限流采用Redis ZSET做滑动窗口,并发数控制用“计数器+过期时间”实现,部门和应用的额度用Redis INCR配合Lua脚本。如果公司已经有Sentinel或Hystrix,也可以借用来做调用端保护,但网关层的集中管控还是得自己在网关上做。

3.3 配额扣减的原子性:Redis Lua才是正解

配额控制最容易出问题的是并发场景。网关一般是多实例部署,多个请求同时到达,如果先用GET读当前已用额度、判断是否超限、然后用SET写回,在并发下一定会出现竞态条件,导致实际超限。我们这个坑踩得很惨:上线第一天,某个部门的瞬时并发冲高,实际消耗超过了月配额的20%,就是因为读写没做成原子操作。

正确做法是把“判断+扣减”放进一个Redis Lua脚本里,脚本在Redis服务端原子执行。核心逻辑如下:

-- KEYS[1]:配额计数key,例如 quota:dept:12:month:202501:cost -- ARGV[1]:本次请求预计消耗的量 -- ARGV[2]:配额上限 local current = tonumber(redis.call('GET', KEYS[1]) or '0') local cost = tonumber(ARGV[1]) local limit = tonumber(ARGV[2]) if current + cost > limit then return 0 end redis.call('INCRBY', KEYS[1], cost) return 1

用法也简单,检查时直接调用脚本。如果返回0,说明配额不足,拒绝请求;返回1说明扣减成功,放行。这里有一个细节:对于token型配额,预扣后实际usage返回时会有偏差,我们的做法是先把预估token预扣,等实际usage回来后再做一次“修正扣减”,把差额补上或退回。所以配额表需要额外记录“预扣量”和“实际量”,靠定时任务做对账。

3.4 超限之后怎么办:拒绝、降级、排队

超限后的策略一开始我们只做了“拒绝”,后来发现太粗暴。核心业务被限流直接报错,业务方体验很差。于是我们加了两层优化:

第一层是降级。超额不严重的部门,自动把模型从pro降到lite,保证功能可用但成本下降。这需要在配置里给每个部门设置降级路径,比如gpt-4o -> gpt-4o-mini,或者在国内模型之间切换。降级有两种触发方式:时间窗口(如晚上低峰期自动降级)和成本阈值(如本月花费达到预算80%后自动降级)。

第二层是排队。对于非实时任务,比如批量数据处理、离线生成,超额后不直接拒绝,而是丢进任务队列,等配额恢复或系统空闲时再执行。这个在网关层做成“异步转发”模式,很多批量场景都适用。

对于真正实时性要求高的场景,比如智能客服、在线对话,超限后只能返回429并带上Retry-After头,让客户端稍后重试。我们实测下来,客户端只要正确遵循429语义,用户体验基本无感。

4. 实操落地:一版可以直接参考的网关方案

4.1 架构与技术选型

这套网关我们生产环境用Go写了核心转发组件,下面演示用Python代码表达同样的逻辑,因为可读性更好,核心流程是完全一致的。整体架构分三层:

  • 接入层:Nginx负责TLS终止、域名路由、最基础的IP黑名单。这个层面不做过重的业务逻辑,避免拖慢转发。
  • 网关服务层:负责鉴权、路由、配额检查、费用计量、流式透传、审计日志。这是升级的核心,也是代码量最大的部分。
  • 存储层:Redis保存配额计数、滑动窗口数据和应用配置;MySQL保存部门、应用、调用流水;报表服务定时从MySQL聚合数据生成账单。

模型服务这一层,我们统一使用OpenAI兼容的接口协议,不管是接云厂商还是内部私有化模型,网关都以标准格式转发,这样路由和计量逻辑不用为每个厂商单独写一套。

4.2 核心中间件实现:从鉴权到记账

网关服务的核心是一个请求处理中间件,按顺序执行鉴权、配额检查、转发、计量。用Python伪代码表达核心流程:

async def proxy(request): # 1. 从请求头/app_key解析应用身份 app_key = request.headers.get("X-App-Key") app_info = await get_app_info(app_key) if not app_info: return JSONResponse(status_code=401, content={"error": "invalid app_key"}) # 2. 解析目标模型和调用方式 model = request.json().get("model") api_type = request.json().get("api_type", "chat") # 3. 配额检查(Redis Lua,原子操作) estimated_tokens = estimate_tokens(request) allowed = await check_quota( dept_id=app_info.dept_id, app_id=app_info.id, model=model, cost=estimated_tokens ) if not allowed: return JSONResponse(status_code=429, content={"error": "quota exceeded"}) # 4. 转发到模型服务(支持SSE流式) async with httpx.AsyncClient() as client: async with client.stream("POST", upstream_url, json=request.json()) as resp: # 流式场景:逐帧解析并透传,同时收集usage async for line in resp.aiter_lines(): if '"usage"' in line: usage = parse_usage(line) await record_usage(request_id, usage) yield line # 5. 记录流水 await write_ledger(request_id, app_info, model, usage)

这个流程有三个关键点:应用鉴权不能只校验key存在,还要校验“这个应用是否被允许调用这个模型”;配额检查必须在转发之前完成,否则超额请求已经打到模型服务了,成本已经产生;流式场景下计量不能等整个流结束,要边转发边收集usage,否则流异常中断时连补救数据都没有。

4.3 限额配置与应用权限管理

配额配置我们设计成了两层JSON配置,存Redis里,修改后即时生效,不需要重启网关。部门级配置示例:

{ "dept_id": 12, "quota": { "month_cost_cn": 5000, "day_input_tokens": 2000000, "day_output_tokens": 500000, "qps": 20, "concurrency": 5 }, "auto_degrade": { "enabled": true, "threshold_percent": 0.8, "fallback_model": "pro-lite" } }

应用级配置则记录在MySQL,网关启动时全量加载到本地缓存,变更通过管理后台发布到Redis,各网关实例订阅变更后更新本地缓存的版本号。这里要注意:配置变更一定要有版本号,否则多实例之间缓存不一致,会导致同样的请求在不同实例上判定结果不同。

4.4 参数调优与部署注意点

部署上最值得提醒的是Redis不能成为单点。网关多实例共享一个Redis,如果Redis挂了,配额检查和滑动窗口数据全部拿不到,会导致网关拒绝所有请求。我们做了两层保障:一是Redis做主从加哨兵,自动切换;二是网关本地缓存了配置数据和分钟级的配额计数,Redis不可用时降级为“只记账不限流”,优先保证业务可用,等Redis恢复后再补同步。

超时参数也需要调。大模型接口本身响应慢,一个请求可能几十秒才完成,但网关和上游的连接不能无限等。我们把“连接超时”设置成5秒、“读超时”按模型差异化配置:chat类模型30秒,embedding类10秒。网关到模型服务的连接复用开启keepalive,降低握手开销。

5. 常见问题与排查实录

5.1 我踩过的五个坑,希望你一次都别踩

第一个坑是流式响应漏记usage,前面已经详细说了。上线第一周,所有SSE调用的费用都是0,导致部门账单严重偏低。排查时发现是网关直接透传了流,根本没解析最后带usage的帧。

第二个坑是配额预扣和实际用量偏差过大。我们最初按请求里的max_tokens参数预扣额度,但很多业务方把这个值设得非常大,导致预扣了一次就把配额扣光了,后续正常请求全被拦截。后来改成“按历史同应用的平均token量预扣,实际回来后再修正”,才解决。

第三个坑是Redis缓存抖动导致网关雪崩。报价和配置经常读Redis,大促流量进来时Redis连接数暴涨,部分超时重试又加剧了压力,最后网关整体变慢。后来加了本地缓存和“弱一致”策略,Redis只作为变更通知源,才稳定下来。

第四个坑是限流单位搞混。配置项里“每分钟6000 token”和“每请求6000 token”完全是两个概念。我们有同事把后者当成了前者配进去,结果所有请求都被拦截,排查了两小时。建议所有配额配置强制带单位,并在管理后台展示换算关系。

第五个坑是废弃的应用key没有回收。某个部门换人后key没删,旧系统还在定时调用,费用一直在涨。后来加了“应用活跃度告警”,超过30天未调用的key自动进入回收流程。

5.2 问题排查速查表

现象可能原因排查思路解决建议
报表里费用为0流式响应未解析usage查看网关日志,确认是否收到带usage的SSE帧升级SSE解析组件
部分请求报429配额设置过小或预扣逻辑过激查看配额计数、对比实际token量改用平均值预扣,事后修正
部门间配额互相影响共用同一配额key检查key命名是否包含dept_id确保dept_id隔离
网关响应变慢Redis连接打满查看Redis监控、连接数增加本地缓存,扩大连接池
月度账单和模型方账单对不上模型方有缓存token计价差异拉取模型方原始账单对比补充缓存token计费规则

5.3 上线前自测清单

网关上线前,我们建议至少跑一遍以下自测,能避开大部分生产问题:

  • 用压测工具模拟每个部门的正常流量,确认配额统计和费用统计与预期一致。
  • 人为把配额调低到极限值,确认超限后返回429、降级、排队三种策略都符合预期。
  • 用流式请求压测10分钟,确认usage统计没有漏记。
  • 模拟Redis主从切换,确认网关能降级到“只记账不限流”模式,业务不中断。
  • 对比网关流水总费用和模型服务商账单,误差控制在1%以内。

6. 一点个人体会

这次升级做下来,我最大的感受是:部门费用明细和限流配额,表面上是两个技术功能,本质上是一家公司AI治理规则的落地。技术方案再漂亮,如果费用归集维度不清晰、配额规则定得不合理,上线后还是会一地鸡毛。所以做这件事之前,一定要先和财务、部门负责人把规则对齐:哪些成本算研发、哪些算业务线成本、超出预算后是断还是降级,这些问题不先谈明白,写代码都是白写。

最后再分享一个小技巧:上线之后不要急着把旧API key全部禁用,先让网关记录一段时间的“影子流量”——所有请求正常转发,但同时在日志里标记出“如果按新配额规则,这个请求会不会被拦截”。跑一到两周,用真实流量验证配额配置合不合理,再切换成强制模式。我们就是靠这个方式,把上线后的线上故障几乎降到了零。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 4:31:13

无设计背景?用免费工具快速制作App宣传图,过审上架全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:31:05

B2B营销策略与数字化工具实战指南

1. B2B营销的本质与核心挑战B2B营销(Business-to-Business Marketing)是指企业向其他企业提供产品或服务的商业活动。与面向普通消费者的B2C营销相比,B2B营销具有决策周期长、参与决策者多、订单金额大等特点。根据Gartner的研究报告&#xf…

作者头像 李华
网站建设 2026/9/12 4:30:42

Python开发者转型AI Agent工程师实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:30:23

YooAsset:Unity资源管理的Runtime中心化重构

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:28:36

如何用 ReVanced Manager 给 Android 应用打补丁并安装或导出

如何用 ReVanced Manager 给 Android 应用打补丁并安装或导出 【免费下载链接】revanced-manager 💊 Application to use ReVanced on Android 项目地址: https://gitcode.com/GitHub_Trending/re/revanced-manager ReVanced Manager 是一个运行在 Android …

作者头像 李华
网站建设 2026/9/12 4:26:15

COMSOL与MATLAB协同模拟岩石损伤与裂纹扩展

1. 项目背景与核心需求在岩土工程和地质力学领域,岩石损伤与裂纹扩展的数值模拟一直是研究热点。传统单一软件往往难以完整模拟这一复杂物理过程,而COMSOL与MATLAB的协同工作恰好能弥补这一缺陷。这个项目的核心在于通过MATLAB循环调用COMSOL&#xff0c…

作者头像 李华