跳到主要内容

负载降级是为人类设计的,而 Agent 会放大你正在抵御的风暴

· 阅读需 13 分钟
Tian Pan
Software Engineer

对人类来说,503 意味着一个“稍后再试”的页面和一段咖啡休息时间。对 Agent 来说,503 只是在七次重试中的第一次尝试前那 250 毫秒的挫折,而且规划器(planner)已经开始询问 LLM 是否有其他工具可以绕过这个失效的依赖项。第一种行为为过载的服务提供了恢复空间。第二种行为则是过载服务的噩梦:数以千计的关联重试,每一次都比人类的操作更廉价、更快速,其中一半还会扩散(fan out)到下一个依赖项,因为规划器认为那是一个富有创意的变通方案。

负载脱落(Load shedding)—— 即通过丢弃低优先级任务来维持高优先级路径可用的准则 —— 是在流量发送主体主要是键盘前的人类,或者是具有手动调优重试策略且行为良好的服务的时代设计的。当 Agent 集群出现时,这两个假设都会瞬间崩塌。Agent 重试速度更快,能同时从更多地方发起重试,绕过故障重新规划,并把你返回的 503 视为负载均衡的暗示,而不是你本意中希望达成的协作式背压(back-pressure)信号。

本文将探讨为什么标准的负载脱落策略在面对 Agent 客户端时会失效,上游服务需要什么样的原语才能真正卸载 Agent 流量,以及 Agent 本身在工具层和规划层必须做些什么,才能不再成为别人事故报告中的恶意流量。

为什么现在 503 的意义截然不同

HTTP 错误的语义从未被完整地书面记录。它们是服务器和客户端之间的一种契约,这种契约之所以有效,是因为客户端大多是由拥有共同心智模型的人类编写的:429 意味着减速,503 意味着我们已满,Retry-After 是你应该遵守的提示。浏览器、CDN 和行为良好的 SDK 都将这一契约内化了。重试风暴曾被视为 Bug,而且这些 Bug 足够罕见,以至于运维团队可以为它们命名。

Agent 并不共享这一契约。典型 Agent 的工具层将每一个非 2xx 响应都视为一个可以重试的瞬态波动(transient blip),因为设计这种循环的 LLM 是在 Stack Overflow 的代码片段上训练出来的,在那些片段中,带退避算法(backoff)的重试是通用的好建议。更糟糕的是,当重试最终耗尽时,规划器并不会去喝咖啡休息 —— 它会询问模型该怎么办,模型会建议使用另一个能得到类似答案的工具,于是 Agent 现在开始冲击一个完全不同的依赖项,而这个依赖项从未在关联需求下进行过压力测试。

最终状态是大多数平台团队至少见过一次的情景:Agent 集群的出现看起来与协调一致的 DDoS 攻击毫无区别,只不过操作者的意图是友好的。流量特征是统一的,时序是关联的,请求模式不像人类驱动的会话那样多变,而且背压信号被忽视了。根据你的 WAF 所调优的每一项特征,这些 Agent 就是 攻击。

三大放大器

当涉及到 Agent 时,有三个乘法因子会将微小的上游波动演变成停机故障。

更快的重试。 人类的请求循环内置了冷却时间 —— 人类会阅读错误信息,切换标签页,一分钟后刷新。Agent 的循环则不然。一个简单的工具调用重试策略会在第一次重试后的一秒内开始第二次尝试,四秒内开始第三次,在任何人类尺度的系统察觉到上游异常之前,就已经烧掉了六次重试。指数退避(Exponential backoff)有所帮助,但前提是它确实被接入了;在实践中,许多 Agent 框架提供的重试原语其默认值是根据人类的延迟预算调优的,而不是为了上游的生存。

关联的扇出。 当一万名人类用户遇到 503 时,他们各自根据自己的时间感做出反应。当一万个 Agent 线程遇到 503 时,它们会在 同一 时刻重试,因为它们运行着相同的重试策略和基础延迟。抖动(Jitter)是标准的解决办法,但抖动是你必须记得添加的功能 —— 而大多数工程师复制粘贴的默认重试装饰器中并不包含它。更糟的是,当 Agent 被部署为托管池(例如一个每个节点运行 N 个 Agent 工作线程的后端服务)时,重试风暴会在节点级别聚集,因此单个上游波动会演变成来自单个源 IP 的同步浪潮,这会在错误的层级触发你的速率限制器,使其看起来像是一个异常的客户端,而不是一个集群。

绕过故障重新规划。 这是人类时代负载脱落机制无法防御的一点。当工具 A 失效时,规划器会询问模型该怎么办。模型会 —— 乐于助人且流利地 —— 建议工具 B 可能可以实现类似的目标。工具 B 有自己的依赖图、自己的速率限制和自己的配额池。现在,你的脱落机制导致负载发生了 转移,而不是消失。用户可见的失败率保持在较低水平(Agent 最终成功了),但服务成本翻了一番,而原本应该吸收溢出流量的依赖项悄然成了下一个失效的目标。在最坏的情况下,规划器发现并行调用工具 A 和工具 B “更稳健” —— 现在每一次重试都变成了两个请求而不是一个。

为什么标准原语不再适用

大多数负载分流(load-shedding)实现将请求分为不同的优先级段。Netflix、AWS 和 SRE 规范都描述了相同的模式:付费客户处于顶层,其次是登录用户,再往下是匿名用户,最底层是已知的爬虫。当系统过载时,你会自下而上地丢弃请求。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates