1. 从一次线上事故说起为什么通用限流在 LLM 场景下会失灵去年年底我负责的一个 AI 应用上线了。功能不复杂用户输入一段需求描述后端调用大模型生成结构化结果再返回给前端渲染。上线第一周流量平稳一切正常。第二周做了一次推广瞬时并发从个位数跳到三位数然后系统就崩了。崩的方式很典型所有请求都在等 LLM 接口返回线程池被占满上游网关开始大面积超时健康检查失败容器被重启重启后流量再次涌入循环往复。典型的雪崩。我第一反应是加限流。项目里本来就集成了 Sentinel加个流控规则不是分分钟的事于是我在 Controller 入口处配了 QPS 限流阈值设成 50。重新上线流量高峰再来结果依然崩了只是崩得慢了一点。问题出在哪我盯着监控看了很久才想明白入口 QPS 限流管不住 LLM 调用的实际并发压力。一个请求从进入 Controller 到真正调用 LLM中间可能经过参数校验、Prompt 组装、上下文检索、历史消息拼接等一堆步骤耗时从几十毫秒到几秒不等。入口限流放过了 50 QPS但这 50 个请求可能在同一时刻全部汇聚到 LLM 调用点上形成远超预期的瞬时并发。这就是标题里说的核心问题AI 接口的高并发和秒杀场景的高并发本质上是两回事。秒杀的特点是请求极短、处理极快、瓶颈在数据库扣减库存AI 接口的特点是请求处理链路长、单次调用耗时高、瓶颈在外部 LLM 服务的并发承载能力。用同一套限流思路去套必然出问题。这篇文章就是把我踩过的坑、试过的方案、最终落地的架构完整拆一遍。如果你正在做 AI 应用开发尤其是涉及 LLM 调用的后端服务这些经验应该能帮你少走不少弯路。1.1 秒杀高并发和 AI 接口高并发的本质差异先把这两类场景掰开揉碎对比一下不然后面的方案选型没有依据。秒杀场景的典型特征请求处理路径极短从接收到返回可能就几十毫秒瓶颈集中在数据库的行锁竞争或库存扣减请求之间高度同质化可以用缓存、队列、令牌桶等手段快速削峰失败请求可以直接丢弃用户重试成本低。AI 接口场景的特征完全不同。一次 LLM 调用的端到端耗时通常在 2 到 30 秒之间流式输出的话更长瓶颈不在本地资源而在外部 API 的并发配额和响应速度每个请求的 Prompt 内容不同无法简单缓存结果请求失败后重试成本高因为已经消耗了 token 和时间更麻烦的是LLM 调用往往涉及多轮交互一个用户请求可能触发多次模型调用。我用一个表格把关键差异列出来这样更直观对比维度秒杀场景AI 接口场景单请求耗时10-100ms2-30s瓶颈位置数据库行锁外部 LLM 并发配额请求同质化极高低Prompt 各不相同失败重试成本低高消耗 token 和时间削峰手段队列缓冲、缓存并发闸门、排队等待超时容忍度低高用户愿意等看明白这张表就能理解为什么入口 QPS 限流在 AI 场景下不够用了。QPS 限的是每秒进来多少请求但 AI 场景真正要限的是同时有多少请求在调用 LLM。这两个指标之间隔着一整条处理链路链路越长、耗时越不稳定偏差就越大。1.2 入口限流为什么管不住 LLM 调用点我当时的入口限流配置大概是这样Sentinel 的 FlowRule 挂在 Controller 方法上QPS 阈值 50超出的请求快速失败。看起来没问题但实际运行时出现了三种典型偏差。第一种是时间窗口错配。入口 QPS 是均匀放行的但 LLM 调用点的并发取决于每个请求在链路中的停留时间。如果某个时刻下游检索服务变慢请求在链路中堆积即使入口 QPS 没超LLM 调用点的并发也会飙升。第二种是重试放大。LLM 调用失败后业务代码里有重试逻辑一次失败可能触发 2 到 3 次重试。入口限流只算了原始请求没算重试请求实际打到 LLM 的压力是入口 QPS 的 2 到 3 倍。第三种是多调用点汇聚。一个用户请求可能触发多次 LLM 调用比如先做意图识别再做内容生成最后做格式校验。入口只算了一个请求但实际产生了三次 LLM 调用。三个调用点如果各自没有限制汇聚起来就是三倍压力。这三种偏差叠加入口限流形同虚设。我后来在监控上看到入口 QPS 稳定在 45 左右但 LLM 调用点的瞬时并发冲到了 180直接把外部 API 的配额打满触发大面积 429。这里有个经验做 AI 应用的限流不能只看入口指标必须找到真正的资源瓶颈点把闸门挂在那里。对大多数 LLM 应用来说这个瓶颈点就是 LLM 调用的汇聚处。2. 并发闸门的设计思路把限流点从入口移到汇聚点想清楚问题之后方案就明确了不再在 Controller 入口做 QPS 限流而是在所有 LLM 调用的必经之路上挂一个并发闸门。这个闸门控制的是同时有多少个 LLM 调用在进行中而不是每秒有多少请求进来。这个思路的转变很关键。QPS 限流是速率控制并发闸门是容量控制。速率控制适合处理短平快的请求容量控制适合处理长耗时、资源敏感的操作。LLM 调用就是典型的后者。2.1 并发闸门的核心原理与选型考量并发闸门的本质是一个信号量。每个 LLM 调用开始前申请一个许可调用结束后释放许可。许可总数就是允许的最大并发数。超过的请求要么排队等待要么快速失败取决于业务需求。选型上我考虑过几种方案。Java 自带的 Semaphore 最简单但功能太基础没有排队超时、没有动态调整、没有监控指标。Guava 的 RateLimiter 是速率限制不是并发限制不适用。Resilience4j 有 Bulkhead 模式功能不错但和现有 Sentinel 体系不兼容。最终还是选了 Sentinel因为项目里已经在用而且 Sentinel 的并发线程数流控模式正好匹配这个需求。Sentinel 的并发线程数流控和 QPS 流控是两种不同的模式。QPS 模式统计的是每秒通过的请求数线程数模式统计的是当前正在处理的请求数。对于 LLM 调用这种长耗时操作线程数模式才是正确的选择。配置上我在 LLM 调用的统一入口方法上加了 Sentinel 资源注解流控模式选线程数阈值根据外部 API 的并发配额来定。比如外部 API 允许 100 并发我就设 80留 20% 的余量应对突发和重试。2.2 为什么选择 Sentinel 而不是其他方案这里展开说一下选型理由因为很多人在技术选型时容易纠结。Sentinel 的优势在于它和 Spring Cloud 生态集成度高注解式配置开箱即用控制台可以实时调整规则不用重启服务监控指标也够用。对于已经在用 Spring Cloud 的项目引入成本几乎为零。另一个考虑是动态规则调整。LLM 服务商的并发配额可能会变业务高峰期和低谷期的承载能力也不同。Sentinel 的控制台可以实时推送规则变更不用改代码不用重启这在生产环境非常实用。还有一个隐性优势是降级策略的灵活性。Sentinel 支持快速失败和排队等待两种流控行为。快速失败适合非核心功能排队等待适合用户愿意等的场景。LLM 调用通常属于后者用户可以接受等几秒但不能接受直接报错。当然 Sentinel 也不是没有缺点。它的线程数统计是基于信号量的不是真正的线程池隔离所以如果调用方没有正确释放许可会导致许可泄漏。这个问题后面会详细讲怎么排查。2.3 闸门阈值怎么定从外部配额反推阈值设定是并发闸门最关键的一环。设高了没效果设低了浪费资源。我的方法是从外部 LLM 服务的并发配额反推。假设你用的是某云厂商的 LLM API文档里写了默认并发配额是 100。那你的闸门阈值应该设多少我的经验是设成配额的 70% 到 80%也就是 70 到 80。留出的余量用于应对三个情况一是重试请求二是其他服务共用同一个 API Key 的情况三是配额统计的延迟误差。如果外部 API 没有明确的并发配额那就用压测反推。逐步增加并发数观察错误率和响应时间。当错误率开始上升或响应时间明显劣化时那个并发数就是实际承载上限闸门阈值设成它的 70%。还有一个细节如果你的应用部署了多个实例闸门阈值要除以实例数。比如总配额 100部署了 4 个实例每个实例的闸门阈值应该是 20 左右。或者用 Sentinel 的集群流控模式统一管理总配额。集群流控配置复杂一些但更精确。3. 落地实操从零搭建 LLM 调用汇聚点的并发闸门理论讲完了接下来是实操部分。我会把完整的搭建过程拆成几个步骤包括依赖引入、资源定义、规则配置、监控接入。代码基于 Spring Boot 和 Sentinel其他技术栈的思路类似。3.1 环境准备与依赖引入先确认你的项目环境。我用的版本组合是 Spring Boot 2.7.x、Spring Cloud 2021.x、Sentinel 1.8.x。版本不用完全一致但 Sentinel 建议用 1.8 以上线程数流控的支持更完善。依赖引入分两部分。如果项目已经集成了 Spring Cloud AlibabaSentinel 的依赖应该已经有了。如果没有需要手动加dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId version2021.0.5.0/version /dependency如果不用 Spring Cloud Alibaba直接用 Sentinel 核心包也行dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-core/artifactId version1.8.6/version /dependency dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-annotation-aspectj/artifactId version1.8.6/version /dependency配置文件里加上 Sentinel 控制台地址方便后续动态调规则spring: cloud: sentinel: transport: dashboard: localhost:8080 port: 8719 eager: trueeager: true这个配置建议加上它让 Sentinel 在应用启动时就建立和控制台的连接而不是等到第一次调用才初始化。生产环境里这个配置能避免首次调用时的延迟。3.2 定义 LLM 调用汇聚点资源关键一步找到所有 LLM 调用的汇聚点在那里定义 Sentinel 资源。什么叫汇聚点就是所有 LLM 调用都必须经过的那个方法。通常我会封装一个统一的 LLM 客户端类所有调用都走它的chat或completion方法。这个方法的入口就是汇聚点。Service public class LlmClientWrapper { private final ChatClient chatClient; public LlmClientWrapper(ChatClient chatClient) { this.chatClient chatClient; } SentinelResource( value llm_call, blockHandler handleBlock, fallback handleFallback ) public LlmResponse call(LlmRequest request) { return chatClient.call(request); } public LlmResponse handleBlock(LlmRequest request, BlockException ex) { // 被限流时的处理逻辑 throw new LlmConcurrencyLimitException(当前请求过多请稍后重试); } public LlmResponse handleFallback(LlmRequest request, Throwable t) { // 调用异常时的降级逻辑 return LlmResponse.fallback(t.getMessage()); } }这里有几个细节要注意。blockHandler处理的是被 Sentinel 限流的情况fallback处理的是业务异常。两个方法的参数列表要和原方法一致最后多一个异常参数。方法名可以自定义但要在注解里对应上。如果你的 LLM 调用有多个入口比如同步调用和流式调用分开那就要定义多个资源或者把它们收敛到一个统一的方法里。我倾向于收敛因为汇聚点越少闸门越精确。3.3 配置线程数流控规则资源定义好之后配置流控规则。有两种方式代码硬编码和控制台动态配置。生产环境推荐控制台配置代码里只做兜底。代码方式大概是这样PostConstruct public void initFlowRules() { ListFlowRule rules new ArrayList(); FlowRule rule new FlowRule(); rule.setResource(llm_call); rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); rule.setCount(80); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rules.add(rule); FlowRuleManager.loadRules(rules); }FLOW_GRADE_THREAD就是线程数模式count是并发阈值CONTROL_BEHAVIOR_DEFAULT是快速失败。如果你希望请求排队等待而不是直接失败可以改成CONTROL_BEHAVIOR_RATE_LIMITER但要注意排队超时时间不能让用户等太久。控制台配置更简单登录 Sentinel Dashboard找到对应的资源新增流控规则阈值类型选线程数单机阈值填 80。保存后规则会实时推送到应用不用重启。注意线程数模式的阈值是单机阈值。如果你部署了多个实例每个实例都会独立计算。总并发等于单机阈值乘以实例数。要控制总并发要么把单机阈值调小要么用集群流控。3.4 监控接入与告警配置规则配好之后必须接监控否则出了问题你都不知道。Sentinel Dashboard 本身有实时监控能看到每个资源的通过 QPS、被限流 QPS、当前线程数等指标。但这些数据只保留几分钟不适合长期观察。生产环境建议把指标导出到 Prometheus 或 Micrometer再接 Grafana 做看板。Sentinel 提供了 Prometheus 的适配器引入依赖后配置一下就能暴露指标dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-prometheus-adapter/artifactId version1.8.6/version /dependency告警方面我配了两条核心规则。一是当前线程数持续超过阈值的 90% 超过 1 分钟说明容量快满了需要扩容或调低阈值。二是被限流的请求比例超过 5%说明用户已经在受影响需要关注。这两条告警帮我提前发现了好几次容量问题。有一次是外部 API 响应变慢导致线程数堆积告警提前 10 分钟触发我有时间做临时扩容避免了用户投诉。4. 踩坑记录那些文档里不会写的实战问题方案落地过程中踩了不少坑有些是 Sentinel 本身的特性有些是 LLM 场景特有的。这部分可能是全文最有价值的内容因为文档里通常不会写这些。4.1 许可泄漏最隐蔽的并发闸门杀手许可泄漏是并发闸门最危险的问题。表现是明明没有那么多请求但闸门一直显示满载新请求全部被限流。原因是某些调用路径没有正确释放许可。Sentinel 的线程数统计是基于entry和exit配对的。SentinelResource注解会自动处理这对操作但如果方法内部抛出了未被捕获的异常或者有异步调用就可能出现配对失败。我遇到过一次LLM 调用超时后业务代码里用了CompletableFuture做异步重试但重试逻辑没有走 Sentinel 的 entry/exit导致原始调用的许可没有释放。修复方法是在异步逻辑里手动处理或者把异步调用也纳入 Sentinel 资源管理。排查许可泄漏的方法是看 Sentinel 的当前线程数和实际并发数是否一致。如果当前线程数居高不下但实际请求很少基本就是泄漏了。临时解决办法是重启实例根本解决办法是检查所有调用路径的 entry/exit 配对。4.2 流式调用的特殊处理LLM 的流式调用streaming和同步调用在并发闸门上有区别。同步调用是申请许可、调用、释放许可的线性流程流式调用则是申请许可、建立连接、持续接收数据、释放许可中间过程可能持续几十秒。如果流式调用也用线程数模式限流那一个流式连接就会占用一个许可长达几十秒闸门很快就被占满。这时候需要区分对待同步调用和流式调用用不同的资源设置不同的阈值。我的做法是给流式调用单独定义一个资源阈值设得小一些因为流式连接本身就更占资源。同时给流式调用加一个最大持续时间限制超过就强制断开释放许可避免连接泄漏。4.3 多实例部署下的阈值计算单机阈值乘以实例数等于总并发这个计算看起来简单但实际部署时容易出错。问题出在实例数不是固定的。K8s 环境下HPA 会根据负载自动扩缩容实例数在 2 到 10 之间波动。如果单机阈值固定是 20那总并发就在 40 到 200 之间波动完全不可控。解决方案有两种。一是用 Sentinel 的集群流控由 Token Server 统一分配配额每个实例按需申请。这种方式精确但配置复杂需要额外部署 Token Server。二是把单机阈值设小按最大实例数来算。比如总配额 100最大实例数 10那单机阈值就设 10。这种方式简单但浪费资源低谷期实例少的时候容量用不满。我最终选了集群流控因为业务对并发控制要求高不能接受波动。Token Server 部署了一个独立实例配置了嵌入式模式成本可以接受。4.4 常见问题速查表把上面这些问题和解决方法整理成表格方便排查时快速定位问题现象可能原因排查方法解决方案闸门持续满载但请求很少许可泄漏对比当前线程数和实际并发检查 entry/exit 配对重启临时恢复流式调用很快占满闸门长连接占用许可查看流式调用平均时长单独资源、单独阈值、加超时限制总并发超出预期多实例阈值叠加确认实例数和单机阈值集群流控或按最大实例数设阈值限流比例突然升高外部 API 变慢查看 LLM 调用响应时间临时扩容或调低阈值规则不生效资源名不匹配检查 Dashboard 资源列表确认注解 value 和规则资源名一致这张表建议收藏。我把它贴在团队 wiki 上新人遇到问题先查表能解决 80% 的常见故障。5. 效果验证与容量规划闸门上线后的真实数据闸门上线后我做了两周的观察和压测这里把真实数据分享出来供参考。5.1 上线前后的关键指标对比上线前的那次事故LLM 调用点的峰值并发冲到了 180错误率 35%P99 响应时间超过 60 秒。上线后同样的流量高峰峰值并发稳定在 78 左右错误率降到 2% 以下P99 响应时间回到 15 秒以内。被限流的请求比例在高峰期大约是 8%这些请求会收到当前请求过多的提示用户可以选择重试。虽然有一部分用户体验受影响但整体系统稳定性大幅提升没有出现雪崩。还有一个意外收获因为并发受控LLM 调用的平均响应时间反而降低了。原因是外部 API 在高并发下会排队响应时间劣化并发控制在合理范围内外部 API 处理更顺畅单次调用更快。5.2 容量规划的实用方法容量规划的核心是搞清楚三个数外部 API 的实际承载上限、你的业务能接受的等待时间、以及两者之间的平衡点。外部 API 的承载上限用压测测。逐步加压记录错误率和响应时间找到拐点。业务能接受的等待时间由产品定比如用户最多等 10 秒。然后反推如果单次调用平均 5 秒10 秒等待意味着最多排 2 轮那并发数就是闸门阈值乘以 2。实际规划时还要考虑峰谷差异。高峰期阈值可以调高低谷期调低节省资源。Sentinel 支持按时间生效的规则可以配置不同时段的阈值。5.3 后续优化方向目前的方案还有优化空间。一是做优先级队列核心业务的 LLM 调用优先放行非核心的排队等待。二是做自适应阈值根据外部 API 的实时响应时间动态调整闸门阈值。三是做多 API Key 的负载均衡把并发分散到多个配额上。这些优化我还在探索中有进展了再单独写一篇。当前的方案已经能解决 90% 的问题对于大多数 AI 应用来说够用了。最后分享一个我在实操中的体会限流方案没有银弹关键是找到真正的瓶颈点把闸门挂在那里。入口限流、并发闸门、队列缓冲每种手段都有适用场景。LLM 场景的特殊性在于调用链路长、单次耗时长、外部依赖重所以并发闸门比 QPS 限流更合适。想清楚这个逻辑方案自然就出来了。
企业数字化 ERP 产品动态
相关推荐
Vert.x 4 Future 与 Promise 全面解析:异步编程的核心实践指南 说实话,我最初看 Vert.x 文档的时候,最头疼的就是 Future 接口。官方示例里一会儿 Future.xxx,一会儿 Promise.xxx,好不容易理清了,又冒出 flatMap 和 compose 的区别,整个人都是懵的。后来我把 Vert.x 4 的… · 2026/9/24 22:57:09
Django教室管理系统实战:从环境搭建到waitress+nginx部署全记录 第一次接到Django作业的时候,任务内容其实挺简单:用Django做一个教室管理系统,能对教室信息做增删改查就行。但真正动手之后我才发现,从环境搭建开始就处处是坑——Python版本选错导致Django装不上、App创建完忘了注册、页面里的图… · 2026/9/24 22:57:09
LLM高并发限流实战:从QPS到并发闸门的架构演进 1. 从一个真实的线上事故说起去年冬天,我们团队负责的一个智能客服系统上线了第三周,流量突然涨了三倍。本来以为是个好消息,结果监控大盘上 LLM 调用成功率从 99.2% 直接掉到 71%,P99 延迟从 2.3 秒飙到 18 秒,用户端… · 2026/9/24 22:57:09
深度学习新闻分类推荐系统:从TextCNN到个性化推荐 简介:这份基于深度学习的新闻分类推荐系统Python实现源码,是专为课程设计与期末大作业准备的高分项目,下载后无需修改即可运行,适用于需要快速交付完整课题的高校学生。系统涵盖新闻数据预处理、文本分类模型训练、推荐逻辑展示等… · 2026/9/24 23:59:53
汽车电子底层软件开发:AUTOSAR与CAN总线实战解析 1. 这门“汽车电子底层软件开发就业课”到底在教什么?——不是写个LED闪烁就能上岗的很多人看到“汽车电子底层软件开发就业课”这个标题,第一反应是:不就是嵌入式C语言单片机CAN通信?刷几道LeetCode、调通一个STM32 CAN收发例程&… · 2026/9/24 23:59:53
Vim基础操作全攻略:保存退出、模式切换与高频命令实战 1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保… · 2026/9/24 23:59:53
Python+CNN车牌识别实战:从数据预处理到模型训练与部署 简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据… · 2026/9/24 23:59:53
AI元人文:从工具使用到思维重构的深度探索 最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决… · 2026/9/24 23:59:53