恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Agent平台线上超时故障复盘:一次工具调用拖垮整个系统
首页
资讯中心
/
Agent平台线上超时故障复盘:一次工具调用拖垮整个系统
Agent平台线上超时故障复盘:一次工具调用拖垮整个系统
发布时间:2026/10/10 20:11:20
开发 Agent Platform踩了一次真实的线上超时故障下午两点半手机连着震了七八次全是告警群的消息。打开监控面板看到可用性从 99.99% 直线跌到 90% 附近第一反应是模型供应商又出问题了——毕竟 Agent 平台对外的体验几乎完全绑定在大模型接口的稳定性上。结果这次猜错了问题出在我们自己的调用链上准确地说是一次不太起眼的第三方工具调用把整个平台拖进了超时风暴。我做的这个项目是一个多智能体调度平台核心工作就是编排任务拿到用户请求后做意图解析、拆解成步骤再依次调用大模型和各类工具检索资料、执行代码、拉取第三方数据最后把结果整合返回。听上去不复杂但真正跑到线上后各种隐藏问题才会暴露出来。这篇就完整复盘这次线上超时故障从表象、误判、根因到修复方案和设计教训都摊开讲清楚。如果你是做 Agent、做平台、做异步任务编排的应该都能找到有用的东西。1. 从告警到失控故障发生时的第一现场1.1 告警指标长什么样先说告警。当时同时触发了三类监控可用性告警核心接口成功率从 99.99% 掉到 90% 左右持续 3 分钟未恢复延迟告警P95 延迟从正常时的 1.5 秒飙升到 18 秒P99 直接超过 40 秒线程池告警核心调度线程池活跃线程数接近最大值队列任务数快速积压。看到这三类告警同时亮起第一判断就是某个下游依赖出问题了。因为 Agent 平台和普通 CRUD 服务不一样它没有简单的缓存可以扛这层缓冲——每次请求背后是一长串实时调用链任何一个环节变慢整个请求都跟着慢。1.2 Agent 平台请求链路的特点我们的一个典型 Agent 任务会经历这样的循环接收用户请求做意图识别和任务规划根据规划结果调用大模型生成下一步动作如果动作需要工具就调用对应的工具接口检索、计算、第三方 API拿到工具结果后再次交给大模型让它决定下一步循环直到任务完成或者到达全局步数上限。所以一个看似简单的用户问题底层可能串了四五次大模型调用中间还夹着好几次工具调用。这意味着一次用户请求的成败取决于链条上最慢的那个环节而每个环节的网络开销、超时设置、重试策略都会产生叠加效应。1.3 从现象到初步定位故障当时我们的第一轮排查动作是打开业务日志看到大量TaskRejectedException说明线程池已经拒绝新任务打开全链路追踪系统发现大量调用链的耗时集中在某第三方数据服务的 HTTP 调用上查看该第三方服务的调用统计P99 从正常时的 800ms 暴涨到 32 秒。到这里表象已经很清楚了下游的一个数据服务响应变慢拖垮了上游的整个线程池。但这只是表象归因真正的根因藏在为什么它会这么快拖垮全局以及为什么我们没有在第一时间拦截住。2. 第一次误判把矛头指向了外部模型服务2.1 为什么第一反应是模型供应商又挂了Agent 平台的日常运维中大模型服务的稳定性是我们最敏感的一根神经。接口动不动就限流、超时、甚至返回异常都是家常便饭。所以故障发生后团队前十分钟几乎都扑在检查模型服务的调用数据上。当时检查了几个点模型服务的错误率正常没有明显上升模型服务延迟P95 维持在 2~3 秒和平时差不多模型鉴权是否出问题没有异常记录。也就是说模型服务并不是这次的瓶颈。但我仍然提了工单去问因为 Agent 平台的体验太依赖模型了谁也说不准是不是对方内部有小概率故障只是我们这边的监控粒度看不到。2.2 错误归因带来的时间成本大概过了十分钟才有人喊了一句你们看全链路追踪那个第三方数据服务是不是有问题这时候我们才把视线从模型服务移开开始仔细看全链路数据。事后复盘这十分钟其实非常宝贵。误判方向本身不可怕可怕的是整个团队在错误的方向上反复确认而线上故障还在持续。这也是监控设计的一个教训光有告警还不够告警必须尽量带上方向性的信息否则大家在慌乱中会按惯性猜测。2.3 真正有用的第一手线索后来我们是怎么快速锁定的靠的是全链路追踪里的两个字段耗时分布故障期间新发起的请求中有 70% 以上的耗时集中在某个 HTTP Span 上错误类型该 Span 的错误主要是Read timed out说明是客户端读到超时不是对方返回失败。这几乎可以断定对方接口本身还在响应但响应速度极慢导致我们的客户端在等待中不断堆积。于是真正的排查才刚开始为什么一个下游变慢会让整个平台几近瘫痪3. 真正的根因线程池耗尽与调用链的放大效应3.1 排查链路从线程池到 JVM 到网络层我们按以下顺序逐一排查看线程池状态核心调度线程池配置的核心线程数为 200最大线程数为 400队列容量 1000。故障时活跃线程数长期维持在 380 以上队列持续打满新任务直接被拒绝看线程堆栈jstack抓了一次线程 dump发现 70% 以上的工作线程阻塞在同一个第三方 HTTP 调用的 socket 读等待中看连接池下游连接池共 50 个连接全部被占满等待获取连接的线程在排队看超时配置核心调度线程池对外部工具调用使用的超时时间默认是 60 秒当时那个第三方服务单次响应已经达到 30~60 秒一个线程一个请求就要等满 60 秒才能释放。到这里根因已经很清晰了局部下游变慢 过长的超时时间 无差别的重试策略 线程池迅速耗尽。3.2 重试风暴看起来没多少流量实际上翻了几倍还有一个隐蔽问题重试。我们当时的工具调用模块里对部分第三方服务设置了失败重试默认重试 2 次。本来在正常情况下这没什么但当对方服务开始变慢时重试变成了灾难第一次调用慢到 30 秒超时失败后立刻重试又等 30 秒第二次重试再等 30 秒。一次工具调用最长可能吃掉 90 秒而这段时间内线程一直挂在这次任务上无法处理任何新请求。上游还在不断发起新请求每个都往线程池里占一个位置然后全部卡在等待中。这就是经典的线程池饥饿现象。3.3 用表格复盘参数配置的问题我把故障前后的关键配置整理成了一张表方便大家对照看问题出在哪配置项故障前故障中问题分析工具调用超时60 秒全局默认无法自动缩短超时过长线程长时间占用重试次数失败重试 2 次每次失败都重试放大下游压力倍增等待时间连接池大小5050全部占满下游变慢时连接池迅速耗尽任务队列有界队列 1000持续打满新任务被拒绝可用性下降线程池隔离无核心线程池共用所有任务共用单点变慢拖垮全局这些配置单独看都不是致命问题但组合在一起就成了一个放大器下游慢一点整个系统就翻车。3.4 Agent 场景为什么会放大这类故障普通 Web 服务遇到下游变慢最多就是请求变慢、用户排队等待。但 Agent 平台不一样一个 Agent 任务内部有循环——它会反复调用大模型、反复调用工具一次任务内可能包含 5~10 次外部调用。这意味着假设一个下游工具变慢导致单次调用耗时从 1 秒变成 30 秒那一个原本只需要 5 次工具调用的 Agent 任务整体耗时可能从 5 秒恶化到 150 秒。而在这 150 秒内线程池中的所有线程都在为一个任务服务。同样的下游故障Agent 平台的放大倍数远高于普通服务这是 Agent 类平台在超时设计上必须特别警惕的原因。4. 修复方案超时治理、隔离舱室与服务降级4.1 给所有外部调用设置分类型超时第一件事是取消那个全局默认 60 秒的懒人配置。我们对所有外部调用按照类型和用途重新梳理了超时时间调用类型推荐超时理由大模型推理调用30 秒模型推理本身耗时长但超过 30 秒大概率是网络或服务问题实时工具调用检索/查数5 秒工具响应通常快5 秒足够覆盖绝大多数情况非核心工具调用辅助信息3 秒拿不到就丢弃不影响主流程整 Agent 任务上限60~120 秒防止单个任务无限循环全局兜底这些超时值不是拍脑袋定的我们是参考了线上 P99 延迟的分布取正常情况下的 P99 加上一定余量。比如工具调用的 P99 是 800ms设置 5 秒超时就是留了约 6 倍余量既不会误杀正常请求又能在故障时快速释放线程。4.2 重试策略只看幂等且必须走退避重试必须有三个前提只对幂等操作重试读操作、单纯的检索操作可以重试写操作、有副作用的操作比如下单、发消息坚决不重试限制重试次数最多 1 次超过就直接放弃重试必须带退避抖动第一次失败后至少等待 500ms 再重试随机加 0~200ms 抖动避免同一时刻大量请求集中重试。这个改动非常关键。故障期间最早的 2 次重试策略让请求数直接翻了三倍改成最多 1 次重试后请求量最多只会翻一倍而且退避机制能给下游留出恢复窗口不会形成对下游的二次冲击。4.3 线程池隔离按依赖的重要程度拆池原来的问题是所有任务共用同一个核心线程池任何一个依赖变慢都会占满全部线程。我们重新设计了线程池结构核心编排线程池负责 Agent 的主循环只做调度和编排不做任何网络 IO 等待大模型调用线程池单独一个池专门处理模型推理调用配独立超时和连接池工具调用线程池再单独一个池按工具类别拆分为多个小组比如检索类、计算类、第三方数据类。这样做的好处是某个工具组的线程池被打满时其他组的任务仍然可以正常运行。用一句通俗的话说就像一栋楼里每户装了独立电表一家跳闸不至于整栋楼停电。4.4 信号量隔离与舱壁模式线程池隔离之外还有一个更轻量级的方案信号量Semaphore。它的特点是只控制并发数不额外占用线程资源。我们在每个工具调用的入口加了一个并发信号量。比如某第三方数据服务最多允许 20 个并发调用超出并发上限的请求直接快速失败Fail Fast而不是排队等待。这有两个直接效果下游变慢时最多只有 20 个线程被这个服务拖住不会继续蔓延超出上限的请求秒败让调用方尽快走降级逻辑而不是把用户挂在那里等 60 秒。这就是舱壁模式的核心思想把对某个依赖的访问限制在一个隔间里即使它炸了也只影响这一个隔间。4.5 降级策略拿不到结果也要让 Agent 走下去第三步是降级。Agent 平台有个天然优势任务流程本身就是弹性的。工具拿不到结果时可以有两种降级方式空结果降级告诉大模型这次检索没拿到数据请基于已有知识回答让流程继续默认值降级某些参数类查询比如汇率、基准值直接返回一个默认值并标注数据延迟。降级方案实施后即使第三方服务完全不可用用户任务也不会卡死只是结果质量会略降。对于绝大多数场景给结果但不够好远胜于一直等然后报错。4.6 全局任务超时兜底最后加了一个总闸任何单个 Agent 任务整体耗时超过 90 秒就强制终止。这个时间从任务开始算起不管内部循环多少次、调了多少工具到了时间就掐断返回给用户一个任务处理超时的明确提示后台再慢慢补跑或重试。这一步是为了防止死循环——比如大模型一直规划同一个动作、工具一直返回异常数据导致 Agent 反复重试没有全局兜底的话单个任务可能吃住一个线程几个小时。5. 复盘清单Agent 类系统设计超时机制的几条铁律5.1 第三方服务的 SLA 绝不等于我们的超时上限这是这次故障最深刻的教训。我们当时对那个第三方数据服务的判断是SLA 稳定、平均延迟低所以没有专门给它设计超时策略而是套用了全局默认值。结果它一旦抖动我们连反应时间都没有。所有外部依赖哪怕是响应速度一直很快的也必须有自己的超时配置和并发上限。稳定性是动态的不是静态的。5.2 超时必须分层越往下越短一个好的超时体系应该是金字塔结构底层每个网络 IO 调用都有短超时3~10 秒中间每个 Agent 步骤有中粒度超时比如 20 秒顶层整个任务有全局超时60~120 秒。每层超时要比上层短这样才会形成快速失败向上反馈的传导机制。如果反过来——任务层 30 秒、调用层 60 秒——那任务层兜底就失效了。超时是层层预警不是最后兜底。5.3 全链路追踪是排查 Agent 故障的第一生产力这次故障如果没有全链路追踪我们大概率还要在模型供应商是否出问题上浪费更多时间。Agent 平台的调用链长、环节多没有可靠的 trace 系统排查故障基本只能靠猜。建议至少做到每次外部调用都记录独立的 span包含耗时、结果、重试次数每个 Agent 任务记录完整的调用链上下文。这在平时可能看不出用处故障发生时就是救命稻草。5.4 演练不能只演成功路径要演依赖故障我们之前做过很多次演练但演练的大多是服务自身故障、机器宕机、流量突发很少演练某个看似不重要的第三方服务变慢的场景。这次故障恰恰是这种边缘场景。之后我们把混沌工程加入了常态化演练随机选一个下游依赖人为注入 10 秒延迟观察系统是否能自动降级、快速恢复。一个不敢拔掉电源的系统就永远不知道自己有多脆弱。5.5 并发和排队是两回事别混在一起这里的经验是并发控制要做到宁可拒绝不要排队。对于 Agent 平台这种延迟敏感的编排系统队列并不能提高吞吐只会让大量请求一起等待然后把迟到的错误又进一步放大。我们后来把大多数调用场景改成信号量 快速失败模式宁可让一小部分请求直接报错重试也不要让大量请求排队等死。写在最后这次故障给我带来的改变故障修复后的一个月里我又回看了很多遍当时的 trace 和线程 dump。说实话这类问题在 Agent 平台里几乎不可能完全避免——你的系统一定会有某个依赖在一个意想不到的时间点突然变慢。技术方案其实都是通用工程手段真正难的是把它们落实到每一个调用细节中。我现在设计任何一个小工具调用都会先问三个问题如果这个调用要等 30 秒系统会怎样如果这个调用被重试两次流量会翻几倍如果这个调用完全不可用任务能不能降级那次故障之后我给自己定了一条规矩每次接入新的外部服务第一件事不是写业务代码而是写超时配置、并发上限、降级策略、trace 埋点——代码之后可以慢慢补这四样东西少了任何一个都别上线。