阐述 AI 代理系统中背压机制的设计方法,通过数据库表 + Worker 循环实现过载保护,避免级联失败。
一个始终在线的 AI Agent 可能在所有进程在技术层面都健康的情况下悄然失败。
队列不断增长,重试次数激增,浏览器会话被持续占用,延迟变得无上限。常见的应对方式是增加 Worker。但这样做可能让故障雪上加霜:更多的 Worker 会消耗同样稀缺的凭证、浏览器槽位、模型上下文或外部 API 配额。
缺失的控制机制是背压(backpressure):一种明确的规则,用于决定何时接受工作、延迟处理、丢弃工作,或请求人工介入。
本文提供一个小型的设计方案,可以用一张数据库表和一个 Worker 循环来实现。它不是基准测试,也不是声称某种队列策略适合所有工作负载。重点是将过载行为变得明确且可测试。
不要从 Worker 数量入手。先找到实际会饱和的那个资源。
常见的 Agent 瓶颈包括:
将限制写成具名预算:
action_budget = {
"browser_sessions": 2,
"model_tokens_per_minute": 120000,
"outbound_messages_per_minute": 30
}
当这些预算中的任何一个耗尽时,即使 CPU 有空闲,进程也已经过载。
排队中的任务不等于正在运行的任务。正在运行的任务也不代表其副作用已成功。要用独立的状态,这样恢复时就不会把旧的租约变成新的重复操作。
tasks(
id,
tenant_id,
idempotency_key,
priority,
state, -- ACCEPTED, RUNNING, SUCCEEDED, FAILED, UNKNOWN, CANCELLED
attempts,
not_before,
lease_until,
created_at,
updated_at
)
唯一约束应该覆盖操作的业务幂等键,而不是随机的任务 ID:
CREATE UNIQUE INDEX tasks_once
ON tasks(tenant_id, idempotency_key);
当 Worker 在分发工具调用后崩溃时,在租约过期后将任务标记为 UNKNOWN。不要悄悄将其恢复到 ACCEPTED。先对提供商进行协调或检查副作用账本。
准入应该是廉价且确定性的。在任务占用浏览器或模型槽位之前就拒绝或延迟它,比在执行中途才发现过载要安全得多。
function admit(task, budget, now) {
if (task.tenant_paused) return { action: "DEFER", reason: "tenant_paused" };
if (budget.in_flight >= budget.max_in_flight) {
return task.priority === "critical"
? { action: "DEFER", reason: "capacity" }
: { action: "REJECT", reason: "over_capacity" };
}
if (task.deadline && task.deadline < now) {
return { action: "REJECT", reason: "expired" };
}
return { action: "RUN" };
}
具体选择取决于操作类型。支付或安全告警可能应该延迟而不是拒绝。过期的刷新任务可能可以安全丢弃。重点在于将这种区分编码化,而不是留给重试默认值来决定。
全局限制是不够的。加上每个租户的并发限制和公平调度规则。
max_in_flight_global = 8
max_in_flight_per_tenant = 2
scheduler = weighted_round_robin(tenant_id)
一个实用的出队查询可以只在两个限制都允许时选择下一个符合条件的任务。如果你的数据库无法清晰表达这一点,可以领取一小批任务,然后在创建租约前事务性地应用每个租户的检查。
避免无界的优先级队列。高优先级应该意味着在有界预算内更早地获得服务,而不是永远饿饿死其他租户。追踪每个租户最旧任务的年龄,使饥饿变得可见。
重试本身就是工作。要将重试计入与首次尝试相同的资源预算。
使用指数退避加抖动,但要限制重试次数和总年龄:
next_delay = min(base_delay * 2 ** attempts, max_delay)
jittered_delay = random_between(0.8 * next_delay, 1.2 * next_delay)
只重试可能是临时性的错误。凭证被撤销、工具 schema 无效、或策略拒绝不应该重试,除非它已升级为故障。
记录每次重试的原因。仅仅一个 attempts=7 的计数器没有以下信息有用:
不要只用平均延迟。一个过载的 Agent 可能保持平均延迟稳定,而一小部分请求却在无限等待。
需要监控的指标:
一个有用的告警不只是"队列深度 > 100"。应该是"最旧未过期任务已等待 10 分钟,而容量报告显示正常"。这指向的是卡住的租约、调度器 bug 或准入不匹配。
背压只有在故障模式被测试过的情况下才是真正的恢复机制。在预发环境中,注入以下场景:
期望的结果是一张状态转换表,而不是"Worker 最终回来了"。
只有当数据库、调度器、租约和协调循环在普通重启中存活时,队列策略才有用。对于始终在线的 OpenClaw 或浏览器自动化部署,我建议用以下清单来评估托管方案:
如果你不想在笔记本上组装这些组件,托管选项如 Ampere 上的托管始终在线 Agent 托管值得评估。这是一个托管建议,不是某个正常运行时间或吞吐量结果的保证。你仍然需要为你的工作负载验证持久化、隔离、备份和恢复保证。
在增加 Worker 数量之前,先回答以下问题:
增加 Worker 只能在瓶颈确实是 Worker 时提高吞吐量。如果瓶颈是凭证、浏览器槽位、模型上下文或出站配额,明确的背压通常是更安全的首选变更。