ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent高并发稳定性实战:缓存、限流、负载均衡与熔断四层防护

Agent高并发稳定性实战:缓存、限流、负载均衡与熔断四层防护 Agent接口在高并发场景下和普通HTTP接口的稳定性完全是两回事。普通接口几百毫秒就能返回Agent请求动辄几秒甚至几十秒中间还套着多轮上下文、工具调用和模型推理。只要外部依赖抖一下或者热点请求涌进来用常规的QPS限流和超时兜底很容易把整条链路拖挂。真正能扛住的做法是四层防护一起上缓存负责承接热点限流控制入口流量负载均衡分散压力熔断在故障出现后快速止损。这四层不是简单叠加中间件而是重新梳理Agent请求从进入到返回的完整生命周期。这篇内容适合正在做Agent服务部署、接口治理、线上稳定性建设的工程同学尤其是已经发现“普通接口的防护套路放在Agent上不够用”的人。1. Agent高并发为什么比普通接口更考验稳定性1.1 耗时模型完全不同先看一个最基础的差异普通接口请求耗时一般在几十到几百毫秒一个请求占用的资源是短时的线程池处理完就释放。Agent请求不一样一次完整的处理链路可能包括接收用户问题、拼接上下文、调用模型、执行工具、生成回复单个请求耗时很容易到几秒甚至几十秒。这个差异直接影响稳定性设计。普通接口限流看QPS就够了Agent接口只看QPS会出大问题。假设一个Agent请求平均耗时15秒QPS只有10系统同时处理的请求就是150个。如果每个请求还带着上下文和临时缓存内存和线程资源很快会被占满。所以Agent高并发场景不能只算每秒进来多少请求还要算系统同时能撑住多少个进行中的请求。并发数、排队数、连接持续时间这些指标比单纯QPS更能反映系统真实负载。1.2 慢请求会被放大最终形成雪崩第二个差异是Agent接口依赖链条更长。普通接口可能只读数据库或调用一个内部服务Agent请求通常会拆成多个子任务意图识别、知识库检索、模型生成、工具调用、外部API请求。任何一个环节变慢整个请求都会变慢。更麻烦的是上游调用方看到超时后往往会重试重试又继续加剧下游压力。下游越慢上游越堆积堆积又导致更多超时最终形成雪崩。我踩过最典型的一次事故就是模型服务本身只是响应变慢但Agent服务没有单独的限流和熔断结果大量请求卡在线程池里等待后面新的请求也进不来整组服务全部不可用。1.3 Agent稳定性建设要重新设计链路理解了上面的差异就能明白为什么“缓存限流负载均衡熔断”这四个词虽然老但在Agent场景下每个都要重新设计一遍。缓存不能只缓存HTTP响应还要缓存系统提示词、工具定义、检索结果、Token级别的上下文。限流不能只看QPS还要看并发数、Token消耗、工具调用频率。负载均衡不能只看机器是否存活还要看实例内部依赖是否健康长连接怎么分配。熔断不能只统计HTTP错误码还要把慢调用、工具超时、半开探测都纳入状态机。下面对照实际落地顺序把每一层拆开讲。2. 第一层防护缓存不是简单复用响应2.1 Agent场景到底缓存什么普通接口缓存的是“响应体”Agent场景如果也这么干命中率会非常低。原因是Agent请求上下文高度个性化同一个系统提示词不同用户、不同轮次的上下文完全不同直接缓存最终回复几乎不可用。真正值得缓存的是那些重复价值高、重建成本高的内容。按我自己的实践按优先级排大概是这些系统提示词模板和角色设定多数Agent应用共用一套或几套Prompt模板这部分可以按模型、应用ID、版本缓存。工具定义和函数描述工具Schema一般不会频繁变化按工具ID缓存即可。知识库检索结果RAG场景下相同或近似问题的检索结果可以复用尤其当知识库内容更新不频繁时。热点问题的固定答案某些高频问题的答案可以预生成命中时直接返回不走模型推理。KV Cache或中间层Token状态长对话场景下如果使用了KV Cache在同一会话内部可以缓存中间状态减少重复计算。但这个受显存和内存限制落地时要评估成本。2.2 缓存失效三大坑穿透、击穿、雪崩缓存这块最容易被忽略的就是失效问题。不是把数据放进Redis就完事失效瞬间才是系统最容易出问题的时候。穿透是指查询一个不存在的数据缓存里没有数据库或模型服务也没有。每次请求都穿透到下游恶意请求或者大量无效输入会把下游打爆。解决思路是布隆过滤器拦截明显不存在的key也可以临时缓存空值但要注意空值TTL不能太长。击穿是指某个热点key在失效瞬间大量请求同时去重建缓存。比如一个热点问题答案缓存了1小时过期那一刻正好有大量用户同时提问所有请求都去调用模型生成重建成本瞬间放大。解决思路是加互斥锁只让一个请求重建其他请求短暂等待后读取新缓存或者提前异步续期不让热点key真正过期。雪崩是指大量key在同一时间集中失效导致下游压力瞬时飙升。解决思路是失效时间加随机偏移不要让所有key在同一秒过期。2.3 缓存更新策略先更新还是先失效Agent场景的缓存更新跟普通接口不太一样。普通接口背后是数据库可以明确知道数据什么时候变了。Agent背后是模型服务、工具服务、知识库变更时机并不总是明确。例如工具定义改了系统提示词换了知识库重新导入了这些都是需要让缓存失效的信号。我的建议是引入版本号所有缓存key里带上资源版本版本一升级旧缓存全部不命中避免“缓存显示旧工具但实际工具已经下线”的脏数据问题。对于更新策略Cache Aside的思路依然适用先更新底层数据再删除或重建缓存。但如果重建成本很高比如需要调用模型生成答案就不能做同步重建而要采用“逻辑过期 异步重建”的方式。缓存内容先设一个较短的逻辑过期时间后台任务在过期前异步生成新值业务请求在读到时发现逻辑过期返回旧值同时触发一次异步刷新。这样可以避免缓存击穿用户也不会因为模型生成而等待太久。注意Agent场景的缓存一致性要求不需要做到百分百强一致。大部分场景下用户接受一个几秒钟前生成的相似答案远比让用户在线等模型重新推理一遍更舒服。2.4 一套可参考的缓存键和TTL配置下面是一套我在Agent服务里常用的缓存设计不同业务可以按实际情况调整缓存内容key示例TTL失效方式系统提示词模板agent:template:{model}:{app_id}:{version}1小时版本号强制失效工具定义agent:tool:{tool_name}:{version}30分钟版本号强制失效知识库检索结果agent:rag:{query_hash}:{top_k}10分钟知识库更新后清空热点问题固定答案agent:answer:{question_hash}1小时随机300秒逻辑过期异步重建会话中间状态agent:session:{session_id}:{turn}30分钟会话结束时清理判断缓存是否值得加不要只看命中率。命中率低不一定没价值如果重建成本很高哪怕命中率只有10%也能挡掉大量昂贵的模型调用。我一般会先看缓存前后的单请求延迟和模型调用量如果延迟下降明显或调用量下降明显说明缓存设计是有效的如果两个指标都没变化说明缓存的内容选错了需要重新拆解热点。3. 第二层防护限流怎么限才不误伤3.1 限流不能只看QPS前面已经讲过Agent请求本身耗时很长只看QPS会漏掉“并发积累”的问题。一个请求耗时30秒QPS可能只有2但系统同一时刻要处理的会话可能是60个每个会话还占着连接和内存。所以Agent场景的限流至少要有三个维度QPS限流控制每秒进入的请求数量防止突发流量。并发限流控制同时处理中的请求数量防止线程池和内存被打爆。Token或资源配额控制每个用户、每个应用在一定时间内的模型消耗防止成本失控。这三个维度不是替换关系是叠加关系。入口先按QPS粗筛然后判断当前并发数是否达到上限最后再根据Token消耗判断是否放行。3.2 固定窗口、滑动窗口、令牌桶怎么选限流算法本身不复杂但放到Agent场景里要注意适配性。固定窗口实现最简单但临界问题很突出。假设一分钟限制100次如果99次在前59秒用完最后一秒可以放行99次下个窗口一开始又放行100次两秒内流量翻了倍。这个问题在Agent热点场景会被放大因为热点往往就是一瞬间涌进来的。滑动窗口能缓解临界问题但也不是没有代价。滑动窗口依赖时间戳集合存Redis存储和计算开销比固定窗口大而且在窗口边缘依然存在精度误差。热搜词里专门提到“滑动窗口限流存在什么问题”这里补充一下滑动窗口把时间切成更细粒度的小格每格有个计数但Redis里存的是一串计数器多个请求并发写入时需要保证原子性否则统计会偏。即使原子性保证了如果请求分布极不均匀限流依然可能漏掉少数超限请求。令牌桶算法更贴合Agent场景因为Agent业务通常需要容忍一定程度的突发。比如某个运营活动放开后瞬间涌入大量用户如果频率是漏桶的恒定速率用户等待时间会非常久。令牌桶允许先消耗积攒的令牌扛住一次性突发。具体选型建议是入口用令牌桶内部用信号量控制并发再搭配每用户的Token配额。3.3 分布式限流怎么做Redis Lua单机限流在不做集群部署的早期够用但只要服务扩展到多实例就必须做分布式限流。否则同一个用户可能绕过每个实例的本地计数器把整体流量放大到单机限流的N倍。分布式限流的常见实现是Redis Lua脚本利用Lua在Redis里原子执行保证“读计数、判断、加一”这三步不会被并发插队。下面是一个固定窗口限流的示例脚本实际生产建议用滑动窗口或令牌桶local key KEYS[1] local limit tonumber(ARGV[1]) local expire tonumber(ARGV[2]) local current redis.call(INCR, key) if current 1 then redis.call(EXPIRE, key, expire) end if current limit then return 0 end return 1这个脚本简单直观但固定窗口的临界点问题还在。如果要用滑动窗口可以在Redis里用ZSET以当前时间戳作为member和score每次请求先清理窗口外的旧记录再统计窗口内的请求数超过阈值就拒绝。这种方式统计更准确但Redis内存消耗更高。Agent场景下如果限流key粒度很细比如按用户维度需要评估Redis内存能不能撑住。3.4 Agent场景的限流阈值怎么拍限流阈值最忌讳拍脑袋。我一般会先压测找到单机实例的“安全并发拐点”。具体操作思路先用并发1跑通单条Agent请求记录平均耗时和P95耗时。逐步把并发升到2、5、10、20观察延迟和错误率的变化。找到延迟明显抬升或错误率开始飙升的并发数这个就是单机安全并发上限。入口QPS阈值可以按“安全并发 / 平均耗时”来估算再留出20%到30%的余量。如果担心Token消耗再加一层按Token的配额限流例如每个用户每分钟最多消耗10万Token。这里要特别提醒低配环境可以压测但压测结果不能直接放到生产。生产环境的上下文长度、工具调用复杂度、模型服务状态都可能不一样。真正上线前要再用生产流量灰度验证一轮。注意限流不只要加在Agent服务的入口网关Agent内部调用模型和工具的地方也要限流。否则入口限制住了一个Agent请求内部可能循环调用工具十几次照样能把下游打爆。4. 第三层防护负载均衡怎么把请求分到合适的地方4.1 负载均衡不是“平均分发”就完了很多团队部署了Nginx或网关看到流量能分到多台机器就以为负载均衡没问题。Agent场景下平均分发往往反而出问题。原因有两个。第一个是请求能力不均衡。一个Agent请求内部可能包含多次模型调用和工具调用消耗的资源远超普通请求。如果两台机器配置相同但一台正在处理多个重负载Agent请求另一台处理的是轻量问答请求按轮询分发就会造成一台忙死、一台闲死。第二个是连接时长不均衡。Agent流式请求会占用很长的连接时间如果只统计请求数量而不统计活跃连接数长连接会把某个节点的线程和内存慢慢占满。所以负载均衡策略要选对。最少连接模式比单纯轮询更适合Agent场景它能根据每个节点当前活跃连接数来分发新请求降低“一台被打满另一台闲着”的概率。4.2 按模型能力和业务分组路由很多Agent服务不是只有一组实例而是按模型能力分组。比如轻量模型用于路由和意图识别重型模型用于深度推理也可能是同一个应用里有几个不同能力的Agent分别对接不同模型。这种情况下负载均衡层要能分辨请求类型按规则路由。常见做法是在网关层解析请求头或请求体中的model字段、app_id字段然后转发到对应的上游分组。如果所有请求都打到同一个后端模型服务之间的资源隔离会失效。某个重型模型把GPU打满时轻量模型的路由请求也会跟着失败。4.3 长连接、流式响应、连接池要一起考虑Agent接口经常使用流式输出一个连接会持续很久。Nginx代理层的proxy_read_timeout如果沿用普通接口的3秒流式请求必然超时。需要把超时时间调到大于模型首字返回时间和整个流式输出的时长同时配合proxy_buffering关闭让流式内容能及时转发给客户端。Agent服务调用上游模型时连接池也很关键。频繁建立TCP连接会明显增加延迟还可能导致上游连接被占满。正确做法是复用长连接设置合适的最大空闲连接数和每个路由的最大连接数。负载均衡策略选择可以参考这张表策略适用场景Agent场景适配度轮询同配置、无状态、短请求低加权轮询集群配置不均中最少连接长耗时请求、流式输出高一致性哈希需要会话亲和、本地缓存复用中高IP Hash简单会话落地低容易热点倾斜4.4 健康检查不能只看进程存活普通接口的健康检查只需要确认进程没挂。Agent服务的健康检查需要检查内部依赖包括模型服务连通性、Redis连接、向量库连接、工具服务响应。原因很简单如果一台机器的Agent进程还活着但它依赖的模型服务已经挂了继续往这台机器发流量只会让每个请求都失败。健康检查不过负载均衡器就会自动摘除节点这才是真正有效的保护。任务卡住时也要看健康检查的响应时间。如果Agent实例已经排队排到几千个健康检查本身也会变慢。这个时候建议健康检查接口也做成轻量的只检查依赖连通性不做业务逻辑避免健康检查本身又加重负载。负载均衡层还有一个容易忽略的点优雅排空。当一台实例要下线或重启时如果直接把流量切走还在处理中的Agent长请求会被丢弃。需要先让负载均衡器停止分发新请求等待当前活跃请求完成后再关闭实例。5. 第四层防护熔断与降级是最后的止损闸5.1 熔断和限流的边界限流是提前控制在流量还没进入核心链路前就把超出能力的请求挡住。熔断是事中兜底当下游依赖已经出问题通过快速失败防止故障蔓延。两者必须同时存在。只靠限流限流阈值设得再准也挡不住下游某一次突发故障只靠熔断熔断触发前的短暂时间内大量请求依然会涌进坏掉的依赖依然会积累阻塞。Agent场景最容易出的问题就是“慢调用”。Agent请求本身耗时很长如果只看HTTP错误率很多请求不是立即报错而是长时间挂起。等到超时时间到了才失败熔断器看到的失败率可能已经很高但系统已经被拖了很久。所以熔断判断里一定要加入“慢调用比例”这个维度。5.2 熔断状态机关闭、开启、半开熔断器的状态机大家都熟悉关闭态正常放行请求记录成功失败和慢调用比例当失败率或慢调用比例超过阈值进入打开态直接拒绝后续请求快速失败经过冷却时间后进入半开态放少量探测请求如果成功则恢复关闭失败则继续打开。这中间最关键的参数有三个统计窗口大小例如10秒内统计最近的100个请求。窗口越大越稳定但对故障反应越慢窗口越小反应快但容易误判。失败率阈值Agent场景建议比普通接口更严格因为一次失败可能已经消耗了很多模型调用和工具调用。我自己一般先设为50%再根据业务容忍度下调。冷却时间从打开到半开的时间建议不低于30秒。冷却太短下游还没恢复就放大量请求进来熔断器会被反复击穿冷却太长恢复速度太慢。5.3 Agent场景怎么降级熔断只是告诉系统“这个依赖不能再调了”但用户还等着结果所以还要有降级策略。降级不是所有请求都返回失败而是牺牲部分能力保证核心可用。按优先级排列常见降级方式有这些完整Agent流程降级为简化流程不调用工具只做纯文本问答。比如一个Agent本来会查天气、查库存、走支付流程熔断后所有工具都不调只用模型能力回复“当前查询服务繁忙”。实时检索降级为历史缓存知识库挂了的时候可以用最近一次成功检索的缓存结果返回。强模型降级为弱模型调用大模型失败时临时切换到成本更低、速度更快的模型保证有回复而不是没有回复。流式输出降级为非流式当流式连接数过多或者网关压力太大时可以先等待完整结果再一次性返回减少长时间连接占用。这个要看业务是否接受。排队降级并发已经接近上限时先让请求进入队列同时向客户端返回“当前忙碌稍后再试”的提示而不是让客户端无限等待。工具链上的每个外部依赖都应该有自己的熔断器。一个Agent请求内部可能调用多个工具不能因为天气服务挂了就让整个Agent所有请求都失败。工具A熔断不影响工具B更不影响模型生成主链路。5.4 熔断参数设置建议下表是熔断参数的一个参考起点具体数值必须根据压测和线上监控调整参数参考值说明滑动窗口大小10秒内100个请求避免小流量误触发失败率阈值50%可根据业务容忍度调低慢调用阈值3秒到5秒Agent请求本身慢要按接口P95设定慢调用比例阈值30%慢调用触发熔断的关键冷却时间30秒给下游恢复留时间半开最大探测数3到5个探测请求太少容易误判熔断器每触发一次一定要有日志和告警。不要等熔断器自动恢复要人工确认下游依赖是否真的恢复。6. 把四层串起来从最小样例到故障演练6.1 添加顺序先保命再优化四层防护不是一次全部加上而是一步步来。否则出了问题根本不知道是缓存配置错了还是限流阈值设低了。我建议的实现顺序是先给Agent服务加并发信号量限制线程池里同时进行的请求数。这一步最简单但能保证在最坏情况下服务不会被拖死。再加入口限流包括QPS限流和每用户Token配额控制进入流量。然后做缓存优先缓存系统提示词、工具定义、知识库检索结果降低模型调用压力。再接入负载均衡和连接池配置水平扩容分散压力。最后补熔断和降级根据压测结果设定慢调用阈值和失败率阈值。每一步都要配监控确认这一层生效后再加下一层。6.2 每一层看什么监控指标很多团队加了Redis、加了网关、加了熔断器但监控面板上空空如也。实际出现故障时只能靠猜。我建议每层至少盯住这些指标层级核心指标异常信号缓存命中率、重建次数、缓存时间分布命中率突降、重建次数飙升限流被限流请求数、排队长度、Token消耗被限流数持续高位、排队长度不下降负载均衡每节点RPS、活跃连接数、错误率单节点连接数明显偏高、错误率不均熔断熔断状态变更次数、半开请求成功率、降级比例熔断反复打开、半开请求连续失败业务层Agent完成率、P95耗时、工具调用成功率完成率下降、P95持续抬升6.3 压测和故障演练是必须的配置全部就绪后不能直接等上线。要跑一轮压测和故障演练验证四层防护真的能扛住。压测可以从最小样例开始。我先用并发1跑三遍看耗时是否稳定再把并发提高到2、5、10、20记录延迟和错误率拐点。单机找到拐点后再放到集群里压测观察负载均衡是否均匀。故障演练可以从最简单的场景开始停掉一个模型服务看熔断器是否触发看被熔断的请求是否快速失败看其他请求是否还正常。之后再模拟Redis不可用、工具服务超时、数据库连接池耗尽等场景。故障演练的意义不是证明系统不会挂而是确认挂了之后能快速恢复而且故障范围可控。6.4 生产环境常见问题排查清单踩过几次坑之后我发现Agent高并发场景最容易出问题的不是算法而是“超时、重试、队列”这三个基础工程点。下面给一个排查顺序遇到问题时按顺序看不要一上来就怀疑缓存或限流。现象第一步看什么可能原因大量请求超时模型服务和工具服务的耗时依赖变慢而非Agent代码问题缓存命中率很低缓存key设计key里拼入了过多个性化信息限流没生效限流key是否一致单机限流在多实例下失效负载不均衡活跃连接数和权重长连接导致新连接全分到空闲节点熔断器反复打开冷却时间和半开探测数参数太少下游还没恢复就重新放流量某个节点内存打爆一致性哈希路由热点用户都落在同一实例排查时记住一个原则先看日志再改参数。日志里如果能看到请求在哪一段卡住可能直接定位到问题上来就改倍率、改阈值只会把问题藏起来。6.5 最后说一点经验Agent高并发不是靠某一个组件解决的缓存、限流、负载均衡、熔断是层层配合的。缓存拦掉了重复请求热点压力自然变小限流控制住了入口线程池和下游依赖不会被打爆负载均衡让每个节点承担的压力尽量均匀熔断则在下游故障时保住整体可用的最后底线。落到具体项目时最怕的不是配置不够强而是没有根据自己业务的耗时模型去设参数。先跑一次最小样例记录平均耗时、最大安全并发、模型和工具的超时时间再根据这些数据去填限流阈值和熔断参数。前面这些基础工作做扎实了再大的流量进来系统也能稳得住。
返回列表