
我先把话放在开头AI接口的高并发跟秒杀系统的高并发从设计目标到落地手段几乎从头到尾都是反着来的。这个结论是我被账单教训出来的。上个月我在做多租户AI能力中台的并发治理一开始不自觉地把电商秒杀那套方法论带了进来——削峰、限流、横向扩容、无限重试。结果一周之内模型厂商的限流告警被打满某个租户的请求无差别429月账单更是肉眼可见地涨了一截。后来我把并发闸门从最外层网关一路挪到了LLM调用汇聚点才算是真正稳住了局面。这篇文章就是那段经历的完整复盘。我会把秒杀高并发和AI接口高并发的差异拆开讲清楚解释为什么闸门必须挂在LLM调用汇聚点以及具体的代码实现和压测验证过程。适合正在做AI应用后端、准备接入大模型能力的开发者和架构师参考。文章不需要你有秒杀系统的实战经验但只要你对传统高并发有过接触就能很快get到差异点在哪里。1. 秒杀系统的高并发经验放在AI接口上为什么就失效了1.1 两种高并发在流量模型上的根本差异先想清楚一件事秒杀系统处理的请求是什么样的请求进来查库存、扣库存、生成订单、返回结果整个链路在几十毫秒内完成请求跟请求之间没有任何上下文依赖丢了就丢了失败了就重新下一单。所以秒杀架构的核心诉求只有一个挡住瞬时洪峰别让数据库被打挂。前端挡一层、网关限一限、队列削一削把百万级的瞬时请求削成每秒几千的平稳流量就赢了。AI接口高并发完全不是这个玩法。一次普通的LLM调用从请求发出到拿到完整响应常见的是3到10秒流式返回模式下虽然首字快但完整输出往往要更久。这意味着什么假设你的系统QPS只有5但每个请求平均耗时20秒那么在同一时刻挂在上游的请求就有100个。这100个请求都在占用模型服务的算力、都在消耗上下文窗口、都在燃烧令牌费用。QPS这个指标在AI接口场景下基本失真了真正该关注的是同时在途请求数和每分钟消耗令牌数。我做过一次对比实验同一个小型服务用同样的压测脚本分别打一个普通Restful接口和一个LLM代理接口。普通接口100并发轻松扛住响应时间稳定在50毫秒以内LLM代理接口跑到30并发响应时间就开始出现锯齿状波动跑到50并发直接触发了上游服务端的限流一批请求开始报429。这给我留下的印象非常深传统高并发的问题是车子太多路不够宽AI接口高并发的问题是每辆车都开得特别慢而且过路费还特别贵。1.2 有状态会话让横向扩容随便调度失效了秒杀系统能随便横向扩容底层依赖是请求无状态。任何一个请求打到集群里任何一台机器上处理结果都一样。但AI对话场景天然是有状态的同一个用户的连续提问共享同一个会话上下文后一个请求要携带前几轮的对话历史一起发给模型。这种状态可以放在Redis里做外部化存储但每次调用仍需要把完整的上下文组装出来、随请求一起发出去这个动作本身就有成本和延时。更有意思的是对话历史是累积增长的。聊到第5轮的时候单次请求携带的上下文可能只有2K tokens聊到第20轮单次请求携带的上下文轻松涨到10K甚至20K tokens。这直接导致一个现象同一个用户、同一个并发模型下越往后单次请求消耗的资源越多系统能承载的同时在线请求数反而在下降。我见过很多团队用固定并发池去接AI接口前面几天跑得好好的一周之后开始频繁报限流排查半天发现不是代码出了问题而是对话上下文变长了同样的并发数对应的令牌消耗量翻了几倍。1.3 失败重试的代价完全不在一个量级秒杀系统的另一个隐含假设是重试很便宜。扣库存失败重新扣一次。下单超时重新下一单。只要做好幂等控制重试几乎无感。AI接口的重试代价是乘以数倍的重试一次意味着把之前的对话历史重新发给模型再算一遍令牌消耗直接翻倍。如果重试三次那就是三倍的算力成本和至少两次的额外等待时间。更麻烦的是AI接口的错误类型比传统接口更复杂。除了常见的5xx和超时还有429限流、上下文超长错误、内容过滤拒绝、甚至模型自身的随机性导致的结果异常。我在项目里就遇到过一种情况某次服务端一个错误配置导致所有请求在特定提示词下返回空内容客户端SDK没有判断返回内容是否为空直接按调用失败处理然后自动重试结果一个本该返回正常结果的请求硬是在短时间内把整个token配额烧掉了一大半。这些差异汇总起来指向一个结论秒杀场景的高并发设计目标是让系统更扛打AI接口的高并发设计目标是让调用更可控。扛打讲究的是容量规划和横向扩容可控讲究的是配额感知和流量整形。前者可以靠堆机器解决后者必须靠精细化的流量治理才能做到。2. 真正的卡点不是QPSRPM、TPM与密钥配额的三重约束2.1 RPM和TPM到底谁在真正限制你多数主流大模型服务商提供的限流指标不是简单的每秒最多多少次请求而是两组带权指标RPMRequests Per Minute每分钟请求数和TPMTokens Per Minute每分钟令牌数。这两个指标通常同时生效任何一个触顶都会被限流。RPM好理解就是单位时间内的请求次数上限。但TPM才是真正让人头疼的它统计的是每分钟内所有请求消耗的输入令牌和输出令牌的总和。举个例子假设你的API密钥在某个模型上的TPM限额是90K tokens/min。你发一个请求输入prompt用了30K tokens输出completion用了10K tokens这一下就消耗了40K tokens的配额。那么你一秒钟内连续发三个这样的请求直接把一分钟的限额用完了后面的57秒内所有请求即使RPM没到上限也会因为TPM被耗尽而收到429限流。我在实际项目中踩过一个特别典型的坑。业务方临时要做一个批量文档总结功能代码里用for循环配合ThreadPoolExecutor直接并发发送30个请求给大模型每个请求的prompt都带上了完整的文档内容单个prompt大约20K tokens。结果就是运行了大概15秒之后RPM还没到上限TPM先爆了。接下来整整一分钟内这个密钥的所有请求全部返回429连带其他正常的在线对话功能也一起被限流。这个场景给我留下的教训是QPS不高但token消耗速度极快触发的限流是全局性的一旦触发所有业务全部遭殃。2.2 账号级配额与密钥级配额隔离思维必须从一开始就有很多平台上的API密钥配额实际上要分两层看账号级配额和密钥级配额。账号级配额是账号维度共享的一个密钥把账号的TPM打满了其他密钥也一起被限密钥级配额才是每个密钥独立计算的。我见过一个团队把所有内部应用都塞进同一个API密钥里写在一个共享的配置文件里。结果就是某个同事本地调试脚本跑了个大循环把整个账号的并发额度占满线上服务瞬间全挂而且因为共用了同一个密钥事后连排查到底是哪个应用超的都没办法通过密钥维度定位。后来他们连夜把所有业务拆成了独立的密钥才重新获得一个业务的流量异常不会拖垮整个账号的安全边界。所以我的建议从来都是每个独立业务、每个独立租户、甚至每个独立场景都使用独立的API密钥。密钥不只是鉴权凭证它还是一个天然的流量隔离单位。如果你把所有流量都混在一个密钥里就等于主动放弃了最廉价、最有效的隔离手段。这个决策不需要任何额外成本只需要在创建密钥的时候多想一步。2.3 上下文累积效应让静态配额计算彻底失效再深一层由于对话上下文是累加的随着聊天轮数增长每次请求携带的历史消息越来越多输入令牌消耗就在持续增长。还是刚才那个例子假设你开始的时候单请求消耗是2K tokens聊了20轮之后单请求消耗轻松涨到8K甚至15K tokens。同样的QPS消耗的TPM是完全不一样的。这给并发控制带来了一个动态调整的维度。并发闸门不能只限制同时允许多少个请求还要根据每个请求的预估令牌消耗量做加权限制。用信号量控制并发时不要用1个请求占1个信号这种简单的计数方式而要根据prompt的令牌数按比例加权。我在网关层拦截到请求后会估算prompt的令牌数然后按权重 max(1, 预估令牌数 / 1000)占用信号量。这样如果一个请求的prompt是10K tokens它就相当于同时占用了10个信号量。这个机制能非常有效地防止大prompt请求悄悄把TPM打爆的问题——因为你在大流量还没到达模型服务之前就在汇聚点本地把它识别出来了。由这一节的三个约束也引出了另一个问题既然RPM、TPM、密钥配额和上下文累积带来的不确定性这么多那在多业务方、多租户同时调用模型接口的架构环境下我们到底应该把流量控制逻辑放在哪个位置才最合理我的答案就是标题里提到的——LLM调用汇聚点。3. 为什么并发闸门只能落在LLM调用汇聚点上3.1 客户端直连的三大问题散、盲、乱先看反面场景。业务系统直接调用大模型API没有中间层这种架构下你要面对三个问题。第一是散。每个业务方的代码里都有自己的LLM调用代码有的直接用模型厂商的官方SDK有的是自己封装了HTTP请求有的甚至在前端浏览器里直接调用模型接口。限流参数、重试策略、超时设置每一处写的都不一样谁也没法统一治理。改一个上游配置所有业务方的代码都得跟着动。第二是盲。直接在业务代码里调LLM运维视角基本是失明的——你不知道当前总共发出了多少个并发请求不知道每个调用消耗了多少token更没法追踪到具体是哪个业务方、哪个用户消耗了最多资源。出了线上问题连查都无从查起。这里的本质是流量治理的前提是可见性没有统一的流经位置就没有统一的可观测性。第三是乱。密钥散落在各个服务的环境变量、配置文件、甚至代码仓库里。这在团队内部可能勉强可用但一旦涉及多租户或者外部接入方密钥管理基本是一场灾难。某个第三方供应商拿着你的生产密钥调模型你能做的只有事后换密钥无法在事前控制他每秒最多用多少配额。3.2 汇聚点所有模型流量的唯一出入口所谓LLM调用汇聚点就是让所有对大模型API的调用流量都汇聚在同一个逻辑位置统一进行鉴权、配额管理、限流、重试、熔断、审计和密钥托管。在这个架构里业务方不直接接触任何厂商密钥也感知不到上游模型的切换。业务方只面向内部接口内部接口负责把请求统一转发到汇聚点由汇聚点承载真正的外部API调用。为什么叫汇聚点而不是网关或者代理因为从流量角度看它不仅仅是转发请求而是要从多个业务系统把不同格式的请求收敛成一个统一出口在出口处统一施加控制策略。这就像一栋大楼的所有水管最终都要经过总水表和总阀门一样总阀门的开合决定了整个大楼的水压是否正常。没有这个总阀门每个房间的水管都是独立的你根本不知道是哪个房间在过量用水。技术选型上这套汇聚点可以基于现有的API网关二次开发也可以独立部署一个轻量级代理服务甚至可以直接嵌入到你已有的BFFBackend for Frontend层。关键是这个位置要满足四个条件第一所有对外模型调用必须经过它第二它能看到每个请求的完整元数据模型名、预估token数、调用方身份第三它持有所有厂商密钥第四它具备执行限流和排队逻辑的能力。满足这四个条件就可以称得上是一个合格的LLM调用汇聚点。3.3 汇聚点解决的实际问题我在落地过程中发现汇聚点至少解决了四个关键问题。密钥集中托管权限统一收敛。所有厂商密钥全部集中在汇聚点所在的服务端环境变量或密钥管理服务里业务方对模型厂商完全透明。换模型、换密钥、迁移平台业务方代码一行都不用改只需要在汇聚点的配置中心调整。同时出网权限被收敛到汇聚点一个点上运维层面可以很轻松地做防火墙白名单策略——只有汇聚点服务器能访问模型厂商的公网域名其他服务一律不允许。并发闸门有了物理上的落点。限流、排队、令牌桶、信号量这些控制逻辑需要一个地方执行。放在业务方代码里每个业务方各管各的总量控制无从谈起放在最前端网关又因为看不到每个请求的token消耗而容易误判。只有放在汇聚点才能同时看到所有调用方的请求量和每个请求的真实token消耗做出准确的全局调度。重试和熔断不再是重复劳动。没有汇聚点时每个业务方都自己写了一套重试逻辑大家的策略还不一样有的请求瞬间重试3次直接把TPM打爆。在汇聚点统一管理重试策略可以做到指数退避加抖动还能实现针对某个模型持续返回5xx的快速熔断以及在熔断期间向所有调用方统一返回降级提示。请求级缓存有了统一的位置。很多场景下同一个问题问大模型不同用户拿到的回答可能类似甚至一样。在汇聚点做基于语义哈希的请求级缓存命中缓存时直接返回完全不消耗任何token。实测一些内部知识库场景下缓存命中率可以达到15%到20%这笔成本优化只有在能统一看到所有请求的位置才能做到。4. 汇聚点落地实录token加权信号量、排队与熔断重试4.1 三级并发控制组合的设计思路我的最终方案采用了三级并发控制的组合信号量控制同时挂起的请求数令牌桶控制请求速率等待队列做削峰填谷。第一级信号量控制同时正在等待模型响应的请求数量。这一级直接对应模型服务的并发承载上限是防止上游被打垮的第一道屏障。第二级令牌桶控制单位时间内可以发起的请求数用来匹配上游API的RPM限制。第三级等待队列当信号量满了或令牌耗尽时请求进入队列等待而不是直接拒绝。这背后的逻辑是AI接口请求本身就很慢几秒钟的排队等待相对于十秒级的模型响应时间来说在业务侧完全可以接受。排队策略把突发流量平滑成平稳流量比直接拒绝重试要高得多。这里有一个选型上的关键决策为什么不直接用熔断加拒绝而是引入等待队列因为AI接口调用一旦被拒绝调用方通常会自动重试。重试带来的额外令牌消耗和使用排队让请求晚几秒再放行哪个更划算算一笔账就很清楚。排队只是增加了延迟而重试是双倍甚至三倍的算力消耗。所以在AI接口场景下排队优先于拒绝拒绝优先于重试。4.2 核心代码逻辑从简单信号量到token加权我最早只用了RPM限速以为限制住每分钟请求数量就够了结果TPM照样爆。后来改成基于token权重占用信号量的逻辑之后才真正稳住了。下面这段是最终版本的简化代码用Python的asyncio实现class TokenWeightedSemaphore: def __init__(self, max_tokens_per_minute90_000, max_concurrent_requests30): self.max_tokens max_tokens_per_minute self.max_reqs max_concurrent_requests self.current_requests 0 self.current_tokens 0 async def acquire(self, estimated_tokens): weight max(1, estimated_tokens // 1000) while True: if self.current_requests self.max_reqs and \ self.current_tokens weight self.max_tokens: self.current_requests 1 self.current_tokens weight return await asyncio.sleep(0.1) def release(self, estimated_tokens): weight max(1, estimated_tokens // 1000) self.current_requests - 1 self.current_tokens - weight使用方式是在每个模型调用请求进入汇聚点时先根据prompt长度估算token数然后调用acquire获取信号调用完成后在finally块里release。weight的计算逻辑是每1000个token占1个权重这意味着一个10K tokens的请求会占用10个单位的并发配额。表面上看这个逻辑不复杂但它解决了固定并发池无法感知请求大小的痛点让大请求和小请求在配额占用上实现了公平。估算token数的方法我用了两种如果模型有tokenizer库就用tokenizer直接数最精确如果没有就用len(text) / 3这种经验公式粗略估算。实测下来中英文混合文本下除以3的估算误差基本在正负20%以内用于并发控制已经完全够用了。4.3 重试策略指数退避加抖动熔断器保底汇聚点里另一块重要的逻辑是重试和熔断。大模型服务发生偶发超时和5xx是常态重点在于怎么重试。我总结出来三个原则原则一带抖动的指数退避。第一次重试等300ms第二次等600ms第三次等1200ms但每一次都加一个随机偏移量。如果不加抖动几十个请求同时失败、同时重试会产生所谓的重试风暴把已经脆弱的服务直接打垮。加抖动的核心目的就是打破请求之间的同步性。原则二区分错误类型。429限流和5xx应该走不同的重试逻辑。429说明上游配额已满可以等一个稍长的时间再重试甚至可以停下来等下一个配额周期5xx可能是瞬时故障可以相对快一点重试。把不同类型的错误混为一谈要么浪费配额要么错过恢复时机。原则三重试前先确认业务语义是否允许。秒杀场景重试安全是因为幂等控制做得好AI接口调用在业务语义上不一定是幂等的。比如让模型生成一段自动扣款SQL这种操作重试可能会导致重复动作。所以汇聚点一定要区分安全重试和需谨慎重试两种请求类型在配置上分开控制。熔断这块我踩过一个印象深刻的坑。早期熔断阈值设得过于敏感连续3次429就触发全局熔断结果一次小抖动导致所有业务方都被降级到缓存答案用户体验反而更差。后来调整为熔断只针对特定模型和特定请求路径不同业务方之间做隔离避免一个租户有故障所有租户都被降级。熔断后的恢复也采用小流量试探模式先放行少量请求确认上游恢复了再逐步开放全量流量。4.4 压测验证从30%的429率到接近0部署完这套方案之后验证工作必不可少。我当时用JMeter脚本和自写的Python并发脚本从20并发逐步加到200并发重点观察三个指标429限流率、TPM趋势、平均响应时间。指标观察重点429限流率上游返回限流的请求占比目标趋近于0TPM趋势每分钟token消耗是否有超过配额的尖峰平均响应时间加闸门后的排队等待时间是否在业务可接受范围内实测结果让我很受震动没有闸门时100并发下429率超过30%TPM直接被打爆加了信号量和token加权后同样的100并发下429率降到1%以下大多数请求通过排队机制平滑处理整体的端到端响应时间处于业务方可接受的范围内。后来又反复调整了信号量权重系数、退避时间和熔断阈值前后迭代了四五轮才找到当前业务模型下的较优参数组合。这次压测验证给我最深的启发是在AI接口场景下控制并发比对抗并发更重要。秒杀要考虑的是怎么扛住更多请求AI接口要考虑的是怎么让请求发得更省、更稳、更均匀。5. 隐藏的稳定性杀手密钥权限、采样参数与编排平台5.1 密钥泄漏的常见路径与第一道防线聊了这么多架构层面的设计还有一个绕不开的环节就是密钥和权限管理。因为所有流量最终汇聚到一点这一点上的密钥安全就变得极其重要。密钥泄漏最常见的几条路径我列出来希望引以为戒前端直连模型服务把API密钥直接写在前端代码里这是最严重的。哪怕被打包混淆过在浏览器DevTools的Network面板里依然能看见密钥和完整请求。密钥提交进Git仓库开发调试时图省事把密钥硬编码进代码里一提交密钥就永久留存在Git历史中之后即使删除也能从提交记录中翻出来。日志系统记录敏感头把请求日志原样打印如果日志里包含Authorization头密钥就随着日志一起进了ELK或者Splunk等于在一个更广泛的范围里泄漏了密钥。第三方SDK的意外上报部分开源的SDK在出错时会附带HTTP请求头信息用于排障如果密钥被一并上报相当于通过第三方渠道把密钥交了出去。在汇聚点架构下密钥安全的核心原则是密钥永远不离开服务端。业务方的任何请求都不携带模型厂商的API密钥而是由汇聚点服务端统一附加鉴权头。这样即使业务方的请求被抓包了看到的也只是内部网关的应用ID而不是模型厂商的密钥。具体落地上还可以做几件事服务端环境变量里只存密钥的引用ID实际密钥放在密钥管理服务中服务启动时拉取到内存对出网流量做白名单策略汇聚点是唯一被允许访问模型厂商公网域名的服务日志采集层面过滤掉authorization、x-api-key等敏感字段从源头上杜绝泄露。5.2 temperature参数与响应稳定性的隐性关系除了密钥安全还有一个很少被深入讨论的话题模型采样参数对系统稳定性的影响。我看到很多团队在调用LLM时temperature永远用默认值。他们在做高并发压测时发现同一个请求同样的参数返回结果耗时出现了大幅波动。这背后的原理是temperature影响模型在采样阶段对概率分布的保守程度。temperature越高模型越倾向于选择概率相对较低但更有多样性的token这让生成路径更发散输出长度和生成步数都可能变长temperature越低输出更可预测生成步数更稳定。实际经验是在做并发规划时如果业务场景允许把temperature调低一点比如0.2到0.4往往比调高并发更有效——它直接带来了更稳定的响应时间和更低的token消耗。尤其在知识库问答、文档摘要这类对确定性有要求的场景低temperature不仅可控还能明显减少无效输出。这不是玄学是采样机制带来的直接结果。5.3 Dify等编排平台场景中的同源问题最后聊一个跟这个标题强相关的实际场景如果你用的是Dify这类LLM编排平台一定要留意SQL查询内容过多导致LLM返回不稳定这类问题。这个问题的本质就是我在第2节里说的TPM叠加效应——随着上下文内容不断累加同样的并发数消耗的token总量在快速增长最终触达平台配额上限导致返回结果不稳定、超时甚至掉线。应对方式不外乎两个方向第一从源头上控制上下文长度比如在调用前做检索压缩只保留与当前问题最相关的片段第二把并发闸门建在编排平台上游也就是调用汇聚点这一层对所有外发请求的管控都收起到网关层避免业务侧绕过限制直接调用。另外时下很多基于LLM的框架和Agent项目也在解决类似的问题。Agent场景里LLM往往不只是被调用一次而是被循环调用多次每一次调用之间还有工具选择、结果观察等步骤。这个过程中如果没有统一的并发控制一个多轮Agent任务可能就会在一分钟内发出几十次模型请求对配额和成本的冲击比单轮对话大得多。在Agent系统的设计中更需要在编排入口做一个汇聚点把所有模型调用统一收口否则一旦Agent拆解出的子任务数量暴涨底层密钥的配额瞬间就会被耗尽。我在实际项目中给Agent场景做了一套任务级配额预算每个Agent任务进来时给它的模型调用分配一个token预算上限超过预算就强制终止并返回部分结果。这个机制本质上是在LLM调用汇聚点之上再加了一层任务维度的闸门效果非常好避免了Agent死循环或者子任务爆炸把密钥配额打空的情况。写在最后的项目体会整段做下来我最想分享的一点体会是不要怕增加一层汇聚点的复杂度。很多团队觉得加一层中间层是多此一举直接把调用逻辑写在业务SDK里不就行了。但现在几乎每个模型服务商都提供了各自的SDK不同SDK的重试策略和限流行为并不一致一旦某个上游的限流策略调整所有业务方代码都要跟着改。加汇聚点之后这种变化被收敛到一个位置业务方的改动成本从每个服务都要动降到了汇聚点配置改一下。第二点体会是关于验证的。理论推演再完备也一定要靠压测来验证。我在压测过程中反复调整了信号量权重系数、退避时间和熔断阈值前后迭代了四五轮才找到较优参数。如果你也在做类似的事情务必保留压测的完整记录后续参数调优会非常依赖这些历史数据。最后再说一个小技巧在汇聚点实现里给每个经过的请求打上唯一追踪ID把这个ID透传到所有下游日志和监控链路中。这样当线上出现某个请求为什么等了10秒或者某个租户的配额为什么耗尽了这类问题时你只需要顺着追踪ID把整个调用链路的日志串起来很快就能定位到瓶颈到底出在排队、限流、重试还是上游模型返回慢。这套东西不复杂但在AI接口这种慢接口场景下排查效率的提升是实打实的。这个LLM调用汇聚点方案最大的价值不在于做了多少复杂的技术而在于用一个位置统一面对了所有的不可控——上游的限流波动、下游的业务突刺、密钥的暴露风险。在AI接口的并发设计中追求的不是更快和更多而是可控和均匀。这也是我一直强调的那句话AI接口高并发不等于秒杀高并发它需要的是另一种形式的架构智慧。