这次事件实际消耗了什么
长时间运行的 agent 集群并不把开放网络当作文档存储来体验。它体验的是一个拥有极多行的查找表,其中大部分行都位于他人拥有的端点之后。当集群被要求在数周或数月内解析冷僻事实时,它消耗的主导资源不是推理,而是针对第三方基础设施的请求量。
其工程后果是,单一的聚合预算无法表达真正重要的约束。“该主机每分钟容忍 N 次请求,不能更多”是一个针对每个目标的陈述。全局 token 上限或全局支出上限是关于集群自身消耗的陈述,它在结构上无法表示存在于他人速率限制器中的限制。
为什么全局预算作为控制手段会失败
Token 上限和成本上限是滞后指标。它们是根据集群已经执行的工作计算出来的。当全局预算触发时,外部请求早已发出,目标早已承受了它们,任何由此产生的危害——对其他客户的服务降级、防御性封禁、滥用举报——都已经发生。预算在它本应阻止的事件之后才触发。
这不是调参问题,而是范畴错误。全局预算回答的是“我们可以消耗多少?”每个目标的速率限制回答的是“我们现在可以往这里发送多少?”只有第二个问题有一个有界的答案,而超过它就会对第三方造成危害。
这种故障会被扇出放大。假设一个集群运行许多 agent,每个 agent 发出的请求速率在任何理性的运营方看来都算礼貌。单个 agent 的速率是适度的。针对单一目标的聚合速率等于单个 agent 的速率乘以当前正在处理该目标的 agent 数量,而调度器没有内在理由让这个乘积保持很小。Agent 通常被优化为取得进展,而不是节约它们看不见的共享外部资源。如果没有对目标的共享视图,每个 agent 的行为都是正确的,而整个系统的行为却很糟糕。
第二个放大器是重试行为。当目标开始拒绝请求时,把拒绝视为瞬时错误的 agent 会重试,通常采用每个 agent 本地的退避。跨许多 agent 的本地退避并不会降低聚合压力;它会把压力重塑为更平滑、更持久的负载,让目标更难卸载。

设计每个目标的速率预算
有效的控制是附加到每个外部资源标识符——scheme、host,以及在相关情况下还有路径或查询类别——上的速率包络,而不是附加到整个集群上。
在出口处强制执行,而不是在 prompt 中。 系统 prompt 中的指令只是建议性的。一个正处于轨迹中途、面临截止期限,或者只是与指令不一致的 agent,无论如何都会发出请求。强制执行属于出口层:一个所有出站请求都必须经过的单一咽喉点,它为每个目标持有一个 token 桶,并拒绝或排队那些会超出包络的请求。Prompt 可以描述意图;出口层做决定。
一个具体机制:维护一个以规范化目标标识符为键的 token 桶,按目标可容忍的速率补充,突发额度按目标观察到的容忍度设定,而不是按集群的胃口设定。每个出站 HTTP 调用在离开进程之前先获取一个 token。如果没有可用 token,该调用不会静默失败,也不会空转;它向调用方返回一个结构化的延迟。
让调度器看到剩余预算。 延迟只有在编排器能够对其采取行动时才有用。调度器应该能够查询每个目标的剩余 token,并据此重新排序工作:现在运行触及不受约束目标的轨迹步骤,把针对已耗尽目标的步骤推迟到以后。这把速率限制从停滞转变为调度输入。没有这种可见性,一个已耗尽的包络看起来与一个损坏的目标完全相同,agent 会烧掉剩余预算去探测它。
把退避视为一等调度状态。 大多数系统把退避建模为错误路径:出了点问题,等待,再试一次。这种框架对于共享外部资源来说是错误的。处于退避中的目标并没有失败;它是一个包络当前为空的目标,正确的响应是降低所有针对它的工作的优先级,直到包络重新填充。把退避建模为调度器中的一种状态——与排队、运行、完成并列——让编排器能够绕过它,而不是对它累积重试。
诚实的权衡是吞吐量。当一个目标暂时耗尽但集群还有可做的工作时,每个目标的包络会留下未使用的容量。全局预算会花掉那部分容量;包络刻意不这样做。采用这种设计的团队是在选择低度利用自己的 agent,以保持在别人的容忍度之内,而这个选择对长周期任务来说有真实的墙钟时间成本。没有任何配置能消除这种权衡,只有一种配置能把它隐藏起来,直到目标完全封锁集群。
第二个权衡是协调开销。每个目标的桶必须在所有

让速率预算可审计的埋点
速率预算只有在事后可观测其消耗时才算是一种控制。
记录每个目标每个窗口的请求计数,并把它们与产生每个请求的轨迹步骤关联起来。关联才是让日志有用的关键。针对某个主机的请求计数告诉你压力发生了;轨迹步骤告诉你哪个 agent、哪个目标、哪个推理步骤产生了它,而这正是你改变行为而不只是观察行为所需要的。
对包络耗尽告警,而不是对总支出告警。总支出告警在集群很昂贵时触发,而这可能完全合法。耗尽告警在某个特定目标处于或接近其可容忍速率时触发,而这正是先于外部危害出现的条件。第二个告警是可行动的;第一个只是计费信号。
把延迟作为一等指标来跟踪。针对某个目标不断上升的延迟率是一个早期指标,表明集群对该目标的需求超过了目标所能容忍的程度——在目标本身以拒绝回应之前就可见,也远在任何聚合预算触发之前。延迟还揭示了调度质量:一个对某个目标大量延迟、而其他目标却闲置的集群,调度得并不好,而这个指标让这一点变得可见。
最后,把包络本身与流量一起记录。当某个目标的容忍度被修订时——因为联系了运营方,或者因为观察到的行为发生了变化——历史请求计数只有对照当时生效的包络才能被解读。没有这一点,事后复盘就无法区分一个遵守了预算的集群和一个在从未正确的预算下运行的集群。
更广泛的一点是,在长周期上运行的 agent 集群主要不是自身算力的消费者。它们是他人服务的消费者,而稀缺资源是那些服务接受请求的速率。针对全局上限进行调度的编排优化的是错误的量。针对每个目标的速率预算进行调度,在出口处强制执行并对调度器可见,才是与实际约束相匹配的控制。
封面照片:Brett Sayles / Pexels。
