1. 从一个真实的线上事故说起去年冬天我负责的一个 AI 应用平台上线了智能问答功能底层接的是某主流大模型 API。上线第三天运营做了一波推广流量瞬间涨了十几倍。按理说我们做了限流Sentinel 规则配得明明白白QPS 阈值卡在 200理论上不该出事。结果呢网关层确实稳如老狗一个请求都没超但后端服务集体雪崩线程池打满接口响应时间从 800ms 飙到 30 秒最后整个问答功能不可用。排查了一整夜根因让我哭笑不得我们把并发闸门挂错了地方。网关层的 QPS 限流只挡住了入口流量但每个进来的请求在后端会触发多次 LLM 调用——有的做意图识别有的做知识检索后的重排有的做最终答案生成。一个用户请求平均触发 3 到 5 次 LLM 调用200 QPS 的入口流量实际打到 LLM 接口的并发是 600 到 1000。而 LLM 接口的并发承载能力远没有我们想象的那么强。这件事让我彻底想明白一个道理AI 接口的高并发和秒杀场景的高并发根本是两码事。秒杀是一瞬间大量请求抢同一资源AI 接口是单个请求在链路中被放大成多次调用。限流策略如果照搬秒杀那套必然翻车。这篇文章我就把这次事故的完整复盘、后来的改造方案、以及踩过的坑原原本本讲清楚。如果你正在做 LLM 应用开发或者负责 AI 平台的稳定性这篇内容应该能帮你少走至少半年的弯路。2. AI 接口并发和秒杀并发到底差在哪2.1 秒杀并发的本质入口洪峰资源争抢秒杀场景的并发模型很单纯。十万个用户同时点立即购买请求全部打到入口后端要做的核心事情只有一件用最快的速度判断谁有资格然后把绝大多数请求挡回去。库存就那么多抢完即止。这种场景下限流的核心逻辑是入口拦截。你在网关层挂一个 Sentinel 流控规则QPS 超过阈值直接快速失败用户看到活动太火爆完事。后端服务的压力是可控的因为被放行的请求数量是恒定的每个请求的处理逻辑也是轻量的——查库存、扣减、下单几百毫秒搞定。秒杀系统的设计哲学是把压力挡在门外门内保持轻量。2.2 AI 接口的本质单请求放大链路耗时AI 接口完全不是这个逻辑。一个用户问帮我总结一下这份合同的风险点后端链路可能是这样的先调一次 LLM 做意图识别判断这是合同审查类请求调向量数据库检索相关法条和模板拿到 20 条候选调一次 LLM 做重排从 20 条里选出最相关的 5 条把合同全文和 5 条参考拼成 prompt调一次 LLM 生成最终答案可能还要调一次 LLM 做答案合规性检查一个用户请求触发了 4 次 LLM 调用。如果入口 QPS 是 100实际打到 LLM 接口的并发就是 400。而且每次 LLM 调用的耗时是秒级的不是毫秒级。这意味着线程会被长时间占用连接池、线程池的消耗速度远超秒杀场景。更麻烦的是LLM 接口本身有严格的并发限制。大多数云厂商的 API 默认并发配额是几十到几百超过就返回 429。你入口限流做得再好汇聚点没控住照样把下游打挂。2.3 一张表看清两种并发的差异维度秒杀高并发AI 接口高并发压力来源入口瞬时洪峰单请求链路放大请求耗时毫秒级秒级到十秒级资源占用短时 CPU/DB 争抢长时线程/连接占用下游依赖库存服务、订单服务LLM API、向量库、重排服务限流位置网关入口调用汇聚点失败模式超卖、少卖429 限流、超时雪崩核心矛盾抢同一资源调用次数放大这张表是我在事故复盘会上画的当时团队里做电商出身的老哥看完沉默了很久说了一句我们一直用秒杀的脑子在做 AI 的限流难怪出事。2.4 为什么汇聚点才是正确的闸门位置所谓汇聚点就是所有 LLM 调用最终都会经过的那个地方。不管你是意图识别、重排、生成还是合规检查只要调 LLM就必须走这个口子。把并发闸门挂在这里好处有三个第一精准控制实际并发。你限制的是真正打到 LLM 接口的并发数而不是入口的请求数。入口可以放 1000 QPS 进来但汇聚点只允许 50 个并发同时在跑剩下的排队或降级。第二保护下游不被击穿。LLM API 的并发配额是硬约束汇聚点限流能确保你永远不会超过这个配额避免 429 错误引发的连锁反应。第三便于统一治理。所有 LLM 调用的超时、重试、降级、熔断策略都在汇聚点统一配置不用在每个业务模块里重复写。提示汇聚点不一定是一个物理服务也可以是一个统一的 SDK 封装层。关键是要保证所有 LLM 调用都经过它不能有绕过的情况。3. 并发闸门的技术选型与核心原理3.1 为什么选 Sentinel 而不是其他方案限流工具市面上不少Guava RateLimiter、Resilience4j、Sentinel 都能做。我最终选 Sentinel原因很实际第一支持热点参数限流。AI 场景里不同用户的请求优先级不一样VIP 用户和普通用户的 LLM 调用配额应该区分开。Sentinel 的热点参数限流可以直接按用户 ID 做差异化控制这是 Guava 做不到的。第二流控模式丰富。直接拒绝、Warm Up、匀速排队三种模式AI 场景下匀速排队特别有用——LLM 调用本来就慢与其快速失败让用户重试不如排队等一会儿用户体验反而更好。第三熔断降级一体化。LLM 接口不稳定是常态Sentinel 的熔断器可以在下游错误率升高时自动切断避免雪崩。这个能力在秒杀场景里用得少但在 AI 场景里是刚需。第四规则动态推送。通过 Nacos 或 Apollo 配置中心可以实时调整限流规则不用重启服务。LLM 接口的配额经常变动态调整能力很重要。3.2 并发线程数限流 vs QPS 限流这是选型时最容易搞混的地方。Sentinel 支持两种流控指标QPS 和并发线程数。秒杀场景用 QPS 就够了因为请求处理快QPS 和并发数基本成正比。但 AI 场景必须用并发线程数限流原因很简单LLM 调用耗时波动极大同样的 QPS耗时 1 秒时并发是 10耗时 10 秒时并发就是 100。用 QPS 限流你根本不知道实际并发是多少。并发线程数限流的逻辑是当前正在处理中的请求数超过阈值新请求就排队或拒绝。这个指标直接反映了对下游的实际压力是 AI 场景下唯一靠谱的限流维度。我实测下来的经验值如果 LLM API 的并发配额是 100汇聚点的并发线程数阈值设成 80 比较稳妥留 20% 的缓冲应对突发。3.3 匀速排队模式在 AI 场景的妙用Sentinel 的匀速排队Rate Limiter模式是 AI 场景下我最推荐的流控方式。它的原理是请求不直接拒绝而是以均匀的速度放行。比如你设 QPS 为 10那么每 100ms 放行一个请求超出的请求排队等待等待超过超时时间才拒绝。为什么适合 AI 场景因为 LLM 调用本来就是秒级的用户对延迟的容忍度相对高。与其快速失败让用户手动重试不如让请求在队列里等几秒成功率反而更高。配置上有个关键参数超时时间。设太短排队没意义设太长用户等不及。我的经验是设成 LLM 平均耗时的 2 到 3 倍。比如 LLM 平均 3 秒返回超时设 8 秒左右比较合适。3.4 熔断降级策略的配置要点LLM 接口的稳定性是玄学有时候好好的突然就开始超时。熔断器的作用是在下游出问题时快速切断避免线程被大量占用。Sentinel 的熔断策略有三种慢调用比例、异常比例、异常数。AI 场景我推荐用慢调用比例。配置逻辑是统计窗口内如果慢调用超过设定 RT 阈值的调用比例超过阈值就触发熔断。比如设 RT 阈值为 5 秒慢调用比例阈值为 0.5统计窗口 10 秒。意思是 10 秒内有超过一半的调用耗时超过 5 秒就熔断。熔断后的降级策略我一般做两级第一级返回缓存的历史答案如果有第二级返回友好的提示语引导用户稍后重试。绝对不能让用户看到 500 错误。4. 汇聚点闸门的完整落地实现4.1 整体架构设计改造后的架构分三层接入层Spring Cloud Gateway 做入口路由和基础限流这里保留 QPS 限流但阈值设得比较宽松主要防 DDoS 级别的攻击。业务层各个业务服务正常处理逻辑但所有 LLM 调用必须通过统一的 LLM Client SDK。汇聚层LLM Client SDK 内部集成 Sentinel在真正发起 HTTP 请求前做并发线程数限流和熔断判断。关键设计原则SDK 是唯一出口。业务代码不允许直接调 LLM API必须走 SDK。这一点靠代码规范和 CI 检查双重保障——CI 里加了静态扫描发现直接调用 LLM API 的代码直接构建失败。4.2 LLM Client SDK 的核心代码SDK 的核心逻辑不复杂我贴一下关键部分public class LlmClient { private final SentinelResourceWrapper resourceWrapper; private final OkHttpClient httpClient; public LlmResponse call(LlmRequest request) { // 定义 Sentinel 资源资源名统一为 llm_call Entry entry null; try { entry SphU.entry(llm_call, EntryType.OUT); // 真正发起 HTTP 请求 return doHttpCall(request); } catch (BlockException e) { // 被限流或熔断走降级逻辑 return fallback(request, e); } catch (Exception e) { // 业务异常记录并抛出 Tracer.traceEntry(e, entry); throw e; } finally { if (entry ! null) { entry.exit(); } } } private LlmResponse fallback(LlmRequest request, BlockException e) { // 降级逻辑先查缓存没有再返回提示 LlmResponse cached cache.get(request.getCacheKey()); if (cached ! null) { return cached; } return LlmResponse.busy(当前请求较多请稍后重试); } }这段代码的关键点SphU.entry(llm_call)是 Sentinel 的入口所有 LLM 调用共用同一个资源名这样限流规则才能统一生效。entry.exit()必须在 finally 里调用否则并发计数不会释放会导致限流误判。4.3 Sentinel 规则配置详解规则配置我放在 Nacos 里格式是 JSON。核心规则如下[ { resource: llm_call, grade: 1, count: 80, strategy: 0, controlBehavior: 2, maxQueueingTimeMs: 8000, clusterMode: false }, { resource: llm_call, grade: 0, count: 10, strategy: 0, controlBehavior: 0, clusterMode: false } ]第一条规则是并发线程数限流grade: 1表示线程数模式count: 80是阈值controlBehavior: 2是匀速排队maxQueueingTimeMs: 8000是最大排队时间 8 秒。第二条规则是 QPS 限流grade: 0表示 QPS 模式count: 10是阈值controlBehavior: 0是快速失败。这条是兜底防止排队请求无限堆积。两条规则同时生效Sentinel 会取最严格的那个。实际运行中并发线程数限流是主力QPS 限流是保险丝。4.4 参数计算阈值到底怎么定阈值不是拍脑袋定的我有一套计算方法第一步确定 LLM API 的并发配额。这个问云厂商或者看文档假设是 100。第二步留安全缓冲。一般留 20%所以汇聚点阈值设 80。第三步验证单实例承载能力。压测一下看单个应用实例在 80 并发下CPU、内存、线程池是否健康。如果单实例扛不住就水平扩容。第四步计算总实例数。如果单实例能扛 20 并发那 4 个实例刚好 80。但要注意Sentinel 默认是单机限流多实例场景下总并发是各实例之和。如果需要集群限流要部署 Sentinel 的 Token Server。我踩过的坑一开始没注意单机限流的问题4 个实例每个都配 80实际总并发 320直接把 LLM API 打爆。后来改成单实例 20总并发控制在 80才稳定下来。4.5 超时与重试的配合策略限流只是第一道防线超时和重试策略同样关键。超时设置LLM 调用超时我设的是 30 秒。这个值看起来很长但 LLM 生成长文本确实可能耗时这么久。设太短会导致大量正常请求被误杀。重试策略只对特定异常重试比如连接超时、429 限流。重试次数最多 2 次且必须加退避——第一次重试等 1 秒第二次等 3 秒。绝对不能用固定间隔重试否则会加剧下游压力。重试与限流的关系重试的请求也会经过 Sentinel会占用并发配额。所以重试次数不能多否则会挤占正常请求的配额。我的经验是重试请求的配额占比不超过 10%。5. 常见问题与排查技巧实录5.1 限流不生效的排查思路限流配了但没生效是最常见的问题。排查顺序如下第一检查资源名是否一致。Sentinel 是按资源名匹配规则的SDK 里写的是llm_call规则里也必须写llm_call大小写、下划线都不能错。我见过有人 SDK 写llmCall规则写llm_call排查了半天。第二检查规则是否推送成功。Sentinel Dashboard 上能看到当前生效的规则如果规则列表是空的说明推送链路有问题。检查 Nacos 配置、检查 Sentinel 客户端版本、检查网络连通性。第三检查是否走了 SDK。如果业务代码绕过 SDK 直接调 LLM API限流当然不生效。用 Arthas 或者日志确认一下调用链路。第四检查并发计数是否泄漏。如果entry.exit()没在 finally 里调用并发计数会一直涨最后所有请求都被限流。这种情况的表现是限流过于严格而不是不生效。5.2 匀速排队导致请求堆积的处理匀速排队模式用不好会导致请求在队列里堆积最后大量超时。我遇到过一次maxQueueingTimeMs 设了 30 秒结果高峰期队列里堆了几千个请求用户等 30 秒后还是失败体验极差。解决办法有两个一是缩短排队时间我后来改成 8 秒超过就快速失败让用户尽早知道结果二是加队列长度限制Sentinel 本身不支持队列长度限制但可以在 SDK 层加一个计数器队列超过一定长度就直接拒绝。5.3 LLM 返回 429 的应急处理即使做了限流偶尔还是会收到 429。这时候不能慌按以下步骤处理立即动作在 SDK 层捕获 429 异常触发熔断暂停所有 LLM 调用 10 秒。这 10 秒内所有请求走降级逻辑。短期动作检查是不是有突发流量如果是临时调低限流阈值。同时联系云厂商确认配额是否有变化。长期动作如果 429 频繁出现说明配额不够要么申请提额要么做多厂商备份一个挂了切另一个。5.4 常见问题速查表问题现象可能原因排查方法解决方案限流不生效资源名不匹配对比 SDK 和规则配置统一资源名限流过于严格并发计数泄漏检查 entry.exit() 调用确保 finally 中调用请求大量超时排队时间过长查看队列长度和等待时间缩短 maxQueueingTimeMs频繁 429总并发超配额检查各实例阈值之和调低单机阈值或集群限流熔断后不恢复熔断窗口设置过长查看熔断器状态缩短熔断时长降级逻辑不生效异常类型不匹配检查 catch 的异常类型扩大异常捕获范围5.5 几个血泪教训教训一不要相信理论上的并发。我们上线前算过单请求平均 3 次 LLM 调用入口 200 QPS总并发 600LLM 配额 1000理论上够。实际上线后某些复杂请求触发 8 次调用峰值并发直接破千。永远按最坏情况估算。教训二限流阈值要留足缓冲。我一开始把阈值设成配额的 95%觉得充分利用资源。结果 LLM API 的响应时间波动时实际并发会短暂超过阈值触发 429。后来改成 80%再没出过问题。教训三降级逻辑必须提前准备好。事故当晚限流触发后降级逻辑返回的是空答案用户看到一片空白投诉如潮。后来改成返回缓存答案或友好提示投诉量降了 90%。降级不是技术问题是产品问题。教训四监控比限流更重要。限流是事后补救监控是事前预警。我们在汇聚点加了详细的监控当前并发数、排队长度、429 次数、熔断状态。这些指标一有异常就告警能在用户感知前发现问题。6. 监控告警与容量规划6.1 必须监控的核心指标汇聚点的监控指标我列了一个清单缺一不可当前并发线程数实时反映对 LLM API 的压力是最核心的指标排队请求数匀速排队模式下队列长度超过阈值要告警限流触发次数按分钟统计突增说明流量异常或阈值过低429 错误次数直接反映 LLM API 的限流情况熔断器状态打开状态持续超过 1 分钟要告警LLM 调用 P99 耗时耗时突增往往是下游出问题的前兆降级请求占比降级比例超过 5% 要关注超过 20% 要告警这些指标通过 Micrometer 上报到 PrometheusGrafana 做看板Alertmanager 做告警。告警阈值根据历史数据动态调整避免误报。6.2 容量规划的实用方法容量规划的核心问题是当前配置能扛多少流量什么时候需要扩容。我的方法是做压测但不是简单的 QPS 压测而是模拟真实调用链路的压测。用生产环境的请求日志做回放每个请求触发真实的 LLM 调用次数观察汇聚点的并发数和响应时间。压测结果用来画一张图横轴是入口 QPS纵轴是汇聚点并发数。找到并发数接近阈值时的入口 QPS这就是当前配置的容量上限。留 30% 余量就是安全容量。扩容的触发条件连续 3 天每天的高峰期并发数超过安全容量的 80%就该扩容了。扩容方式优先水平扩容应用实例同时按比例调整单机限流阈值。6.3 多厂商备份的切换策略单一 LLM 厂商的风险很高配额限制、服务故障、价格调整都可能影响业务。多厂商备份是必然选择。切换策略我设计了三层第一层主备切换。主厂商正常时全量走主主厂商熔断时自动切到备厂商。切换在 SDK 层完成业务无感知。第二层流量灰度。新厂商接入时先切 5% 流量过去观察一周没问题再逐步放大。第三层成本优化。不同厂商价格不同可以在低峰期把流量切到便宜的厂商高峰期切回稳定的厂商。这个策略需要业务方确认因为不同厂商的回答质量可能有差异。切换的关键是统一抽象。SDK 层定义统一的请求和响应格式各厂商的差异在适配器里消化。这样切换时业务代码完全不用改。7. 一些个人体会这套方案跑了大半年中间又迭代了几次目前支撑着日均千万级的 LLM 调用没再出过雪崩事故。回过头看最关键的认知转变就是标题里那句话AI 接口高并发不等于秒杀高并发。秒杀思维是堵入口AI 思维是控汇聚。入口堵得再死链路内部的放大效应不解决下游照样被打穿。把闸门挂在 LLM 调用汇聚点才是治本之策。另外一点体会是限流不是越严越好。我见过有的团队把阈值设得极低结果大量正常请求被拒用户体验极差。限流的目标是保护下游的同时最大化吞吐不是把请求挡在门外。阈值要基于压测数据来定不能拍脑袋。最后分享一个小技巧在 SDK 里加一个影子模式新规则上线时先只记录不拦截观察一周的实际影响确认没问题再真正生效。这个做法帮我们避免了好几次误配置导致的事故。这套方案不是银弹不同业务场景可能需要调整。但核心思路——找到调用汇聚点在那里挂闸门——应该是通用的。如果你正在做类似的事情希望这篇内容能帮你少踩几个坑。 SEO 优化官网定制响应式建站教育培训建站