ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AI接口高并发≠秒杀高并发:LLM汇聚点并发限流实战

AI接口高并发≠秒杀高并发:LLM汇聚点并发限流实战 1. 从一次线上事故说起为什么秒杀那套限流方案在 LLM 场景下会翻车去年年底我接手了一个 AI 应用的后端治理工作系统本身不算复杂一个对话式产品前端把用户输入发到网关网关做鉴权、路由然后调用后端服务后端服务再去调用大模型接口拿到回复返回给用户。上线初期用户量不大一切正常。直到某次运营活动带来了一波流量高峰问题集中爆发了。现象很有迷惑性网关层的 QPS 限流规则明明生效了Sentinel 的监控面板上通过的请求数被压在了阈值以内但后端服务的线程池还是被打满接口大面积超时大模型调用返回一堆 429 和超时错误整个链路雪崩。当时我第一反应是限流阈值配错了反复核对之后发现阈值没问题——问题出在限流挂的位置根本不对。这个坑其实很典型。做过电商秒杀的同学都知道秒杀场景的限流思路是在入口处把流量闸门一卡超过阈值的请求直接快速失败后面的服务就安全了。这套逻辑成立的前提是——一个入口请求对应一次后端资源消耗且这个消耗是廉价、快速、可预测的。秒杀下单接口查个库存、扣个减几毫秒就完事入口限流等于后端限流一一对应。但 LLM 调用完全不是这个模型。一次用户请求进来后端可能要做这些事拼接上下文、检索知识库、调用一次或多次大模型、处理流式返回、做后处理。其中大模型调用是长耗时、高成本、易失败、有并发上限的操作。更关键的是入口的 QPS 和后端实际发起的 LLM 调用次数不是一比一——一次请求可能触发多次 LLM 调用比如 Agent 场景下的多轮工具调用也可能因为重试放大调用量。你在入口卡 QPS卡住的是用户请求数而不是LLM 调用数这两者之间隔着一层放大系数。所以标题里那句话——AI 接口高并发 ≠ 秒杀高并发——不是文字游戏是我踩了坑之后最真实的体会。把并发闸门挂在 LLM 调用的汇聚点上而不是挂在网关入口才是这类系统正确的治理姿势。这篇就把我这套方案的来龙去脉、设计取舍、落地细节和踩过的坑完整讲一遍适合正在做 AI 应用后端、被大模型调用稳定性折磨过的同学参考。2. 核心矛盾拆解入口限流和 LLM 汇聚点限流到底差在哪2.1 秒杀模型和 LLM 模型的本质差异先把两种场景的流量特征摆到台面上对比差异一目了然。维度秒杀下单LLM 调用单次请求耗时毫秒级秒级到数十秒单次资源成本极低高算力/额度/费用入口请求与后端资源比约 1:11:NN 随场景浮动失败代价低可快速重试高重试会放大压力并发瓶颈位置数据库/库存服务大模型服务端并发上限是否可快速失败可以流式场景下体验差看这张表就能明白秒杀的核心矛盾是瞬时流量洪峰冲击库存而 LLM 的核心矛盾是长耗时调用占满并发槽位。前者靠入口削峰就能解决后者你就算把入口 QPS 压到很低只要每个请求都占着一个 LLM 并发槽位不放槽位照样会被耗尽。我举个具体的数字感受一下。假设大模型服务端给你的账号并发上限是 50你的入口 QPS 限流配的是 100。看起来入口限流比后端并发还宽松好像没问题错。如果每个请求平均耗时 5 秒那么稳态下同时在处理的请求数 QPS × 平均耗时 100 × 5 500。也就是说入口放进来 100 QPS后端实际有 500 个请求在并发跑其中大部分都卡在等大模型返回。50 的并发上限瞬间被打爆剩下的 450 个请求全部在排队或者直接失败。这就是**利特尔法则Littles Law**在起作用并发数 到达速率 × 平均处理时间。秒杀场景处理时间短并发数小入口限流约等于并发限流LLM 场景处理时间长同样的入口 QPS 会放大出几十倍的并发。你只盯着 QPS 这个数字等于闭着眼睛开车。2.2 为什么汇聚点才是正确的闸门位置理解了上面的放大效应闸门该挂哪里就清楚了。所谓LLM 调用汇聚点指的是系统里所有最终会发起大模型调用的代码路径都会经过的那个统一出口。它可能是一个封装好的 LLM Client可能是一个专门的调用代理层也可能是一个统一的网关组件。把闸门挂在这里有三个好处。第一计量准确。你限制的是真正稀缺的资源——LLM 并发调用数而不是一个被放大系数扭曲的入口指标。不管上游怎么重试、怎么多轮调用到了汇聚点都是实打实的一次调用限流才有意义。第二保护精准。大模型服务端的并发上限、你的费用预算、你的密钥配额这些约束都是作用在调用层面的。在汇聚点限流等于直接对着约束条件做控制不会出现入口没超但后端超了的错位。第三全局一致。一个系统里可能有多个入口——Web 端、App 端、开放 API、内部定时任务、Agent 后台任务它们最终都走同一个 LLM 汇聚点。在汇聚点做限流天然覆盖所有来源不用在每个入口重复配置、重复维护也不会漏掉某个新加的入口。提示汇聚点限流不是要取代入口限流两者是配合关系。入口限流负责挡住明显的恶意流量和无效请求汇聚点限流负责保护真正稀缺的 LLM 资源。只做其中一个都不完整。2.3 方案选型为什么是 Sentinel限流组件市面上不少我最终选了 Sentinel理由有几个。一是它对并发线程数这种资源型限流支持得很自然不像有些组件只擅长 QPS 维度二是它的流控规则可以动态调整配合控制台改阈值不用重启三是它和 Spring Cloud Gateway 这类网关集成成熟团队里做微服务的同学本来就熟学习成本低。当然 Sentinel 不是唯一选择Resilience4j、自研令牌桶都能做。选型的核心不是哪个组件更牛而是它能不能表达并发数这个维度的限流。如果你的组件只能按 QPS 限流那在 LLM 场景下就得自己换算换算过程又依赖平均耗时这个不稳定变量很容易失准。Sentinel 的并发线程数流控模式直接对应当前有多少个调用在跑语义清晰这是我选它的主要原因。3. 落地实操把并发闸门挂到 LLM 汇聚点的完整过程3.1 第一步找到并统一 LLM 调用出口动手之前先做一件事——把系统里所有发起 LLM 调用的地方找出来。这一步听起来简单实际很容易漏。我当时的系统里LLM 调用散落在至少四个地方主对话服务、摘要生成服务、一个后台的向量化任务、还有一个做意图识别的轻量调用。如果只在主对话服务上加限流其他三个照样能把并发打满。正确的做法是收敛出口。把所有 LLM 调用统一到一个 Client 封装里业务代码只依赖这个 Client不直接碰底层 SDK。这样汇聚点就唯一了限流、重试、超时、日志、密钥管理全部收口在这一层。public interface LlmClient { LlmResponse chat(LlmRequest request); FluxLlmResponse chatStream(LlmRequest request); }所有业务代码注入LlmClient底层实现可以是任意厂商的 SDK。这个封装层就是后面挂闸门的地方。收敛出口这件事本身价值就很大即使不做限流也建议做它让 LLM 相关的治理有了统一的抓手。3.2 第二步用 Sentinel 定义并发流控规则Sentinel 的并发线程数流控核心是给资源定义一个当前并发数上限。当正在执行的线程数达到阈值新的调用就会被拒绝或排队。配置方式有两种代码硬编码和动态规则生产环境推荐动态规则。先看资源定义。在 LLM Client 的调用方法上打上SentinelResourceSentinelResource( value llm:chat, blockHandler handleBlock, fallback handleFallback ) public LlmResponse chat(LlmRequest request) { return doChat(request); } public LlmResponse handleBlock(LlmRequest request, BlockException ex) { throw new LlmConcurrencyLimitException(LLM 并发已达上限请稍后重试); }然后配置流控规则。关键参数是grade设为RuleConstant.FLOW_GRADE_THREAD表示按并发线程数限流count就是允许的最大并发数FlowRule rule new FlowRule(); rule.setResource(llm:chat); rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); rule.setCount(50); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); FlowRuleManager.loadRules(Collections.singletonList(rule));这里的count 50不是拍脑袋定的要结合大模型服务端的并发上限、你的费用预算、以及实测的 P99 耗时来算。我一般会留 20% 的余量比如服务端上限 60我就配 50避免踩线触发服务端限流。3.3 第三步阈值到底怎么算——一个可复用的计算过程阈值计算是这套方案里最容易被忽视、也最容易出错的地方。我见过太多人直接抄一个100或者200上去结果要么太松保护不住要么太紧浪费资源。分享一套我实际在用的计算流程。先确定约束条件。假设大模型服务端给你的账号并发上限是C_server 60你的费用预算是每分钟最多B 300次调用实测单次调用 P99 耗时T 8秒。第一步从服务端并发约束出发安全并发数C1 C_server × 0.8 48留 20% 余量。第二步从费用约束反推。每分钟 300 次调用平均每次耗时 8 秒那么稳态并发数C2 (B / 60) × T 5 × 8 40。这个数字的含义是如果要把调用量控制在预算内同时每次调用占 8 秒那么平均并发不能超过 40。第三步取两者较小值C min(48, 40) 40作为并发流控阈值。第四步验证。用压测工具模拟 40 并发持续打观察服务端是否触发限流、P99 是否恶化、错误率是否上升。如果一切正常可以小幅上调试探边界如果服务端开始报 429就往下调。注意这个计算里的T一定要用 P99 而不是平均值。平均值会被大量快速返回的短请求拉低掩盖长尾请求对并发槽位的占用。用平均值算出来的阈值会偏乐观实际跑起来容易超。3.4 第四步流式场景的特殊处理流式返回是 LLM 场景绕不开的也是限流最容易出问题的地方。普通请求是调用-返回一个完整周期流式请求是调用-持续推送-结束这个持续推送的时间可能很长如果按普通方式占着并发槽位一个慢速客户端就能拖垮整个并发池。我的处理方式是把并发槽位的持有时间和流式推送时间解耦。具体做法是在发起 LLM 调用、拿到流式响应的那一刻就认为这次调用完成了释放并发槽位后续的推送由独立的推送线程池负责不再占用 LLM 并发额度。public FluxLlmResponse chatStream(LlmRequest request) { // 获取并发许可 Entry entry SphU.entry(llm:chat:stream); try { FluxLlmResponse stream doChatStream(request); // 拿到流对象即释放许可推送阶段不再占用 return stream.doFinally(signal - entry.exit()); } catch (BlockException ex) { entry.exit(); return Flux.error(new LlmConcurrencyLimitException(并发已满)); } }这里有个细节要注意doFinally会在流结束或取消时触发确保许可一定被释放不会因为客户端断开而泄漏。这个泄漏问题我在早期版本踩过客户端频繁断连导致并发许可被占满新请求全部被拒排查了半天才发现是许可没释放。4. 常见问题与排查技巧实录4.1 限流生效了但后端还是被打爆这是最典型的问题八成是限流挂错了位置。排查思路是先确认限流资源是不是真的在 LLM 调用路径上再看是不是有绕过这个资源的调用路径。我遇到过一种情况主流程走了带限流的 Client但某个异步补偿任务直接调了底层 SDK绕过了闸门。解决办法就是前面说的收敛出口让所有调用都必须经过统一 Client。还有一种可能是并发数统计口径不对。Sentinel 的并发线程数统计的是进入资源到退出资源之间的线程数如果你的调用是异步的、线程切换了统计就会失准。异步场景要用SphU.asyncEntry配合手动entry.exit()否则限流形同虚设。4.2 阈值调了没反应Sentinel 的规则默认是懒加载的如果规则推送后没有触发一次规则加载可能不生效。用动态数据源比如 Nacos、Apollo推送规则时要确认监听器正常工作。另外FlowRuleManager.loadRules是覆盖式加载如果你在多处调用它后面的会覆盖前面的导致规则丢失。生产环境建议统一走动态数据源不要散落硬编码。4.3 快速失败还是排队等待Sentinel 的流控行为有快速失败、Warm Up、排队等待三种。LLM 场景我一般用快速失败因为排队等待会让请求在队列里堆积用户等半天最后还可能超时体验更差。快速失败至少能让用户立刻知道现在忙稍后再试前端也好做提示。但有个例外如果是后台的批处理任务比如批量生成摘要用排队等待更合适因为这类任务对延迟不敏感排队能提高吞吐。所以流控行为要按调用来源区分不能一刀切。4.4 常见问题速查表现象可能原因排查方向限流不生效资源未在调用路径上检查是否有绕过 Client 的调用并发统计不准异步调用未用 asyncEntry改用异步 Entry 并手动 exit规则推送无效数据源监听未生效检查 Nacos/Apollo 监听配置许可泄漏异常路径未释放 Entry用 try-finally 或 doFinally 兜底服务端仍报 429阈值高于服务端上限下调阈值并留余量流式请求拖垮并发推送阶段占用槽位解耦调用与推送的许可持有4.5 几个我踩过的坑第一个坑是重试放大。早期我在 Client 里配了自动重试失败重试 3 次。结果限流触发后被拒的请求又去重试重试又被拒反而加剧了并发压力。后来改成限流拒绝的请求不重试只有网络抖动这类瞬时错误才重试且重试要计入并发额度。第二个坑是多实例阈值叠加。系统部署了 5 个实例每个实例配了 50 并发加起来就是 250远超服务端上限。解决办法是用集群流控或者把单机阈值除以实例数。集群流控需要额外的 token server部署成本高一些但阈值控制更精确。实例数不多的话直接按总阈值 / 实例数配单机阈值也能凑合。第三个坑是监控缺失。限流触发后如果没有监控告警你根本不知道系统在什么状态下运行。我后来加了限流触发次数、当前并发数、拒绝率这几个指标接到告警面板上阈值调整才有数据支撑不然全靠猜。5. 一点延伸这套思路还能用在哪把并发闸门挂在汇聚点的思路其实不局限于 LLM。任何入口请求和后端资源消耗不成正比、且后端资源稀缺的场景都适用。比如调用第三方付费 API、访问有连接数限制的数据库、调用有配额限制的短信服务本质都是一样的——你限制的应该是稀缺资源本身而不是一个被放大系数扭曲的入口指标。我后来把这套模式抽象成了一个通用的资源网关层所有对外部稀缺资源的调用都经过它由它统一做并发控制、重试、超时、熔断、监控。LLM 只是第一个接入的资源后面接短信、接支付、接地图服务都是同一套逻辑。这样治理能力就沉淀下来了新接入一个资源只需要配一条规则不用重新设计。如果你现在正在被 LLM 调用的稳定性问题困扰我的建议是先把调用出口收敛掉这是所有后续治理的前提。出口收敛了限流、监控、降级才有地方挂。然后按前面那套计算流程把并发阈值定下来先用一个保守的值跑起来再根据监控数据慢慢调。别一上来就追求精确先保证不雪崩再优化资源利用率。这套东西我前后调了两三个月才稳定下来急不得。
返回列表