ARTICLE DETAIL

资讯详情

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

Agent Platform 线上 504 超时故障复盘:分层超时与 LLM 长尾治理

Agent Platform 线上 504 超时故障复盘:分层超时与 LLM 长尾治理 1. 一次 504 把我从本地跑通的幻觉里拽了出来Agent Platform 这个项目我从零搭到能跑通 demo 大概花了三周。本地环境里用户输入一句话后端调度 LLM 规划任务调用几个工具最后把结果流式吐回前端整个过程丝滑得像演示视频。我当时甚至有点飘觉得这东西离上线也就差个部署脚本的距离。然后上线第二天监控面板上开始零星冒 504。不是每次都复现大概每二三十次请求里蹦出来一次。用户侧看到的是转圈转到天荒地老最后弹一个网关超时我这边看日志请求确实进来了LLM 调用也发出去了但就是迟迟不返回等网关的默认超时阈值一到连接被掐断前端拿到 504。最要命的是这种故障没有稳定复现路径你盯着它它不出现你一转身它又来一下。这篇文章我想完整复盘这次线上超时故障的排查链路以及围绕 Agent Platform 这类 LLM 应用我在超时治理上踩过的坑和最后落地的方案。涉及的关键词包括 Agent Platform、504、超时、LLM、deepseek 这些但我不想写成一篇泛泛的超时处理指南而是把真实故障现场摊开讲——包括我一开始判断错方向、绕了远路的那部分。如果你正在做 LLM 智能体平台、多工具调度、流式响应这类系统这篇应该能帮你少走几天弯路。先说结论方向免得你跟着我一起绕Agent 场景下的超时绝大多数不是网络慢这么简单而是链路太长、超时阈值层层错配、以及 LLM 本身的长尾延迟三者叠加的结果。504 只是最外层网关替你喊的那一嗓子真正的问题藏在里面好几层。2. 先把 504 的来龙去脉理清楚别急着改代码2.1 504 到底是谁抛出来的很多人一看到 504 就条件反射去查后端服务其实 504 Gateway Timeout 的定义很明确网关或代理在等待上游服务器响应时超时。也就是说抛 504 的那个角色本身不是干活的它是中间转发层。它等下游等太久了主动放弃并返回 504。放到我的架构里链路大概是这样用户浏览器 - CDN/负载均衡 - API 网关 - Agent 调度服务 - LLM Provider(deepseek) | - 工具调用(搜索/数据库/外部API)网关有个默认的上游等待超时比如 60 秒。Agent 调度服务收到请求后要先让 LLM 做任务规划再逐步执行工具调用中间可能还要多轮 LLM 交互整个流程跑下来如果超过 60 秒网关就不等了直接 504。这里有个反直觉的点后端服务可能还在正常干活它没崩它只是慢。504 不代表服务挂了代表网关没耐心了。这个认知很关键因为它决定了你排查的方向——你要找的不是哪里报错了而是哪里耗时超预期了。2.2 为什么 Agent 场景特别容易触发 504普通 CRUD 接口一次请求几十毫秒到几百毫秒网关超时阈值基本碰不到。但 Agent Platform 不一样它的单次请求天然就长多轮 LLM 交互一次任务规划可能就要调 2-3 次 LLM每次几秒到几十秒不等。串行工具调用规划出来的步骤往往要一个个执行搜索、查库、调外部 API每个都有网络往返。流式响应为了体验我用的是 SSE 流式返回连接要一直保持到最后一个 token 吐完。LLM 长尾延迟这是最阴的。deepseek 这类模型P50 可能就两三秒但 P99 能飙到几十秒甚至更久尤其是输出长文本或者遇到复杂推理时。把这几个乘起来单次请求总耗时轻松突破网关阈值。而且因为 LLM 延迟是长尾分布大部分请求很快偶尔一个慢的就正好撞上超时——这完美解释了为什么故障是偶发的。2.3 我一开始的错误判断我最初的反应是网络抖动因为 504 看起来就像连接问题。于是我干了这些事加长网关超时到 120 秒、给 LLM 调用加了重试、在调度服务里加了连接池。结果呢504 频率确实降了一点但没根治而且出现了新问题——用户等待时间变长体验更差重试还导致 LLM 调用量翻倍成本上去了。后来我才意识到我把症状当成了病因。加超时阈值只是让网关多等一会儿但请求本身还是那么慢长尾还是存在只是从60 秒超时变成120 秒超时治标不治本。真正的解法得从链路设计和超时分层入手。3. 分层超时让每一层都知道自己该等多久3.1 超时阈值错配是万恶之源排查到后面我发现最核心的问题不是某处太慢而是各层超时阈值没有统一设计互相打架。举个真实例子网关超时 60 秒Agent 调度服务里对 LLM 的 HTTP 客户端超时设的是 90 秒。这意味着什么网关 60 秒就把连接掐了但调度服务还在傻等 LLM 到 90 秒等它终于拿到结果想返回时发现下游连接早断了白干一场还占着资源。这就是典型的内层超时大于外层超时属于设计事故。正确的原则是超时阈值必须从外到内逐层递减外层永远比内层先超时这样内层才有机会做优雅处理比如返回部分结果、记录日志、释放资源而不是被外层一刀切断。我最后落地的分层是这样的层级角色超时阈值说明L1网关/负载均衡90s最外层给足总预算L2Agent 调度服务整体80s留 10s 给网关做响应处理L3单次 LLM 调用30s单轮推理上限L4单次工具调用10s外部 API 上限L5数据库查询3s内部查询上限每一层都比它外面那层小留出缓冲。这样任何一层超时都能在它自己的范围内被捕获和处理不会把整个请求拖死。3.2 总预算制给整个请求设一个 deadline光分层还不够因为 Agent 是多步骤的你不知道它会跑几步。如果每步都卡在各自上限累加起来还是可能超总预算。所以我引入了总预算制deadline propagation。思路很简单请求进来时记一个绝对截止时间戳比如deadline now 80s。然后把这个 deadline 一路透传到每个子调用。每次要发起 LLM 调用或工具调用前先算一下还剩多少时间用剩余时间作为这次调用的超时而不是用固定的 30 秒。import time class RequestContext: def __init__(self, total_budget_ms): self.deadline time.monotonic() total_budget_ms / 1000.0 def remaining_ms(self): return max(0, int((self.deadline - time.monotonic()) * 1000)) def call_llm(ctx, prompt, per_call_limit_ms30000): budget min(ctx.remaining_ms(), per_call_limit_ms) if budget 0: raise TimeoutError(request budget exhausted) # 用 budget 作为本次调用的超时 return llm_client.invoke(prompt, timeout_msbudget)这个改动的价值在于它把超时从一个静态配置变成了动态预算。如果前面几步已经花掉 60 秒那后面只剩 20 秒LLM 调用就会用 20 秒而不是 30 秒做超时避免最后一步把总预算撑爆。实测下来这个改动直接把 504 从偶发压到了几乎为零。3.3 为什么不用固定超时有人会问直接给每层设个固定值不就行了我试过不行。因为 Agent 的任务复杂度差异极大简单任务 5 秒搞定复杂任务要 70 秒。固定超时要么对简单任务太宽松浪费要么对复杂任务太严格误杀。总预算制的好处是自适应简单任务快速通过复杂任务在预算内尽量跑完跑不完就优雅降级。提示deadline 透传时要注意时钟问题。跨进程、跨机器时不要用墙上时钟wall clock用单调时钟monotonic clock或者直接传剩余毫秒数避免机器时间不同步导致的诡异 bug。4. LLM 长尾延迟真正的隐形杀手4.1 长尾延迟为什么这么难缠前面说 LLM 延迟是长尾分布这不是我瞎猜。我专门统计过 deepseek 调用的耗时分布P50 大概 2.5 秒P90 约 8 秒P99 能到 35 秒以上极端情况甚至超过 60 秒。这意味着每 100 次调用里就有 1 次会拖到 35 秒以上。在 Agent 场景里一次请求可能包含 3-5 次 LLM 调用那么单次请求撞上长尾的概率就被放大了好几倍。这就是为什么偶发 504其实一点都不偶发——从概率上讲它必然会发生只是你抓不准是哪一次。4.2 长尾的成因我后来跟几个做 LLM 基础设施的朋友聊加上自己观察长尾主要来自几个方面输出长度不确定LLM 是自回归生成的输出越长耗时越久。任务规划这种需要输出结构化长文本的场景天然容易长尾。推理复杂度波动简单问题秒回复杂推理要想很久。Provider 侧排队高峰期请求排队你的请求在队列里等着延迟自然上去。网络传输流式响应下token 一个个传网络抖动会累积。理解了成因对策就清晰了不能指望消除长尾只能管理长尾。4.3 我的长尾治理组合拳我最后用了几个手段组合第一流式 心跳保活。既然 LLM 是流式输出那就别等它全部生成完再返回。用 SSE 边生成边推同时每隔几秒发一个心跳注释行: keepalive让网关知道连接还活着别急着掐。这一招对网关等太久型 504 特别有效。第二超时降级而非直接失败。如果 LLM 调用超时了不要直接抛错让整个请求失败而是返回一个降级结果。比如任务规划超时就退回到一个预设的简单规划模板先让流程跑起来总比 504 强。def plan_with_fallback(ctx, user_input): try: return llm_plan(ctx, user_input, timeout_msctx.remaining_ms()) except TimeoutError: # 降级用规则模板兜底 return rule_based_plan(user_input)第三对长尾请求做异步化。对于明显可能很慢的任务比如用户要求深度分析干脆不走同步接口改成提交任务 轮询/推送结果。同步接口只处理快任务慢任务异步跑从根上避开网关超时。第四缓存高频规划结果。很多用户的输入是相似的把 LLM 规划结果按输入哈希缓存起来命中缓存直接返回既快又省钱。我实测缓存命中率能到 20% 左右对降低整体延迟帮助不小。注意降级方案一定要提前设计好别等线上出事了才临时想。而且降级结果的质量要能接受否则用户会觉得这系统怎么突然变笨了。5. 排查线上超时的完整链路我是怎么一步步定位的5.1 第一步确认 504 是网关抛的还是应用抛的别小看这一步。我一开始就栽在这以为是应用报的错。后来去翻网关日志发现 504 是网关自己生成的应用日志里根本没有对应的错误记录——因为应用还在跑只是没跑完。确认 504 的来源决定了你后面查哪一层的日志。判断方法很简单看响应头。网关抛的 504 通常带自己的 Server 标识应用抛的会带你框架的特征。或者直接看网关访问日志里面有 upstream response time 字段。5.2 第二步给请求打上全链路 trace偶发故障最怕没有上下文。我给每个请求生成了一个 trace_id从网关一路透传到 LLM 调用和工具调用所有日志都带上这个 id。这样一旦某个请求 504我能把它的完整链路捞出来看到底卡在哪一步。这一步做完问题立刻清晰了卡住的那次请求trace 显示 LLM 调用耗时 47 秒而网关阈值是 60 秒加上前面的工具调用总耗时 63 秒刚好超。真相大白。5.3 第三步统计耗时分布而不是看单次单次日志只能告诉你这次慢了但你需要知道慢是常态还是异常。我把一周的 trace 数据拉出来按阶段统计耗时分布阶段P50P90P99最大值任务规划(LLM)2.8s9s38s62s工具调用0.5s2s8s15s结果汇总(LLM)1.5s5s20s40s端到端5s18s65s95s看到没端到端 P99 是 65 秒已经超过网关 60 秒阈值了。这就从数据上证明了504 不是意外是 P99 必然撞线。有了这个数据优化目标就明确了——把端到端 P99 压到阈值以下。5.4 第四步定位最大贡献者从分布看任务规划 LLM 调用的 P99 是 38 秒是最大头。于是优化重点就放在它身上加缓存、加降级、限制输出长度用更简洁的 prompt 让它少废话。结果汇总那步也做了流式优化。工具调用虽然 P99 有 8 秒但占比小优先级放低。排查超时一定要用数据说话别凭感觉优化。我见过太多人一上来就优化数据库结果瓶颈根本不在那。6. 那些文档里不会写的实操细节6.1 重试是把双刃剑我一开始给 LLM 调用加了自动重试想着失败了再试一次总没错。结果发现重试在超时场景下往往是灾难。因为超时本身就意味着这次调用很慢你再重试一次等于又等一遍总耗时直接翻倍还占着连接和配额。更糟的是如果超时是因为 Provider 侧过载你的重试会加剧过载形成雪崩。后来我改成只对明确的瞬时错误如连接被拒重试对超时一律不重试直接走降级。这个改动让系统稳定性提升明显。6.2 连接池和并发数要匹配Agent 服务调 LLM 用的是 HTTP 连接池。我一开始没注意池大小默认值很小高并发时请求排队等连接又叠加了一层延迟。后来把连接池调大并配合限流避免把 Provider 打爆。这里有个平衡池太小会排队池太大可能触发 Provider 限流得根据实际 QPS 和 Provider 配额算。6.3 流式响应的超时要单独设流式场景下超时要分两种首字节超时和整体超时。首字节超时是指从发起请求到收到第一个 token 的时间这个要设短一点比如 10 秒因为如果 Provider 半天不吐第一个字基本就是有问题了。整体超时才是总预算。很多人只设了整体超时结果首字节卡住时白白等很久。6.4 日志里一定要记录剩余预算在每次调用前后记录剩余预算排查时一眼就能看出是哪一步吃掉了预算。这个习惯帮我定位过好几次问题——有时候不是某一步特别慢而是前面几步各慢一点累积起来就爆了。7. 上线后的效果与几个还没解决的问题改完之后我盯了两周监控。504 从每天几十次降到了基本为零端到端 P99 从 65 秒压到了 45 秒左右用户侧的超时投诉基本消失。LLM 调用成本因为缓存和降级反而降了大概 15%。但也不是全无遗留问题。降级方案的质量是个持续要打磨的点规则模板生成的规划有时候确实不如 LLM 的用户能感觉到差异。另外异步化的任务虽然避开了网关超时但用户要等推送体验上不如同步流畅这块我还在权衡。还有个我至今觉得棘手的地方总预算到底设多少合适。设太短复杂任务跑不完设太长用户等得难受。我现在的做法是按任务类型分档简单任务 30 秒预算复杂任务 80 秒但这个分档规则还在根据数据迭代。如果你也在做 Agent Platform 这类系统我的建议是别等线上出 504 了才想起超时治理。从架构设计阶段就把分层超时、总预算、降级方案想清楚比事后救火省心得多。LLM 应用和传统后端最大的区别就是延迟不可控你得假设慢是常态然后围绕这个假设去设计系统而不是假设它应该很快。
返回列表