Knowledge

无人值守调度的三件机件:认领锁、中心推送、幂等键

无人值守调度的三件机件:认领锁、中心推送、幂等键

无人值守调度三件机件:一枚认领挂锁、一个中心分配轮盘与一把指纹钥匙

系列:Idea-to-Production · 机理篇。同系列:《人不在环内:一个 AI 自主交付组织的概念契约》(概念契约)、《为什么要拆成三个仓库:知识、调度、档案的分离法则》(分层背景)。

“没有任何人在环内”落到工程上,其实是三个非常古典的分布式系统问题:同一个任务不能被两个工人做、同一件周期工作不能被触发两次、同一次重跑不能产出两份结果。 这篇讲调度层的三件机件——原子认领锁、中心推送的作业运行器、幂等契约——以及每个机件里那些”不写下来一定会翻车”的细节。

为什么值得看

大多数多 Agent 系统在用”对话”掩盖调度问题:反正只有一个工人、反正跑砸了重跑一次就行。一旦一个角色要跑多个实例、一旦循环任务上了日程,这三个问题会立刻浮出水面。本文的价值不在于介绍某个具体产品,而在于给出三件任何无人值守调度都绕不开的机件及其设计细节——你可以直接在自家系统里对照检查。

你将学到三点

  1. 并发执行的互斥要”原子认领 + 看门狗回收”双保险:拿不到锁就跳过下一个,绝不死等;锁过期后由巡检自动回收,恢复上限约三分钟,而不是”等人来救”。
  2. 周期任务必须由中心拥有:角色一旦多实例,任何”自己安排下一次”的 Agent 都会把同一件事做多遍。触发权在平台,实例只是被动接收者。
  3. 每个循环都必须声明自己的去重键:重跑不翻倍,靠的不是运气,而是”检查先行”(check-before-create)的显式约定。

机件一:原子认领锁——一个任务只有一个工人

前提:一个角色(比如 PM)可能同时跑着多个实例。执行任何任务前,实例必须先赢得这条任务的认领锁。

一条任务的锁生命周期:

拉取候选(按角色匹配 + 可执行状态 + 优先级)
  → 依赖门控:所有前置任务必须 Done,否则不可认领
  → 原子 INSERT 锁表(任务 ID 是主键 → 物理上只容得下一个持有者)
  → 干活(超过锁窗口前必须续期)
  → 状态上报即释放(校验转移 + 删锁在同一操作内完成)
  ↘ 忘了续期?看门狗巡检 → 强制回 Backlog → 他人可再认领

值得逐条品味的五条协议规则:

规则 内容 为什么这样设计
P1 未认领成功,绝不干活 锁表记录就是分派本身;本地锁是权威,平台同步只是尽力而为
P2 认领失败 = 跳过,绝不重试风暴 拿不到锁的实例看到的就是”没有任务”;不去轮询锁表、不等锁释放
P3 依赖门控发生在认领时 前置未 Done 的任务,执行者根本看不到,不会半成品开工
P4 可能超窗的任务必须续期 锁窗口默认 7200 秒(2 小时),长任务按约每 30 分钟续一次
P5 释放只能经由状态上报 不是持有者来上报?409 拒绝,绝不强写——持有者的报告永远优先

看门狗是这套协议的兜底:巡检每 60 秒跑一轮,过期锁一律带 expired 标记释放、任务强制回 Backlog 并可记 EXPIRE 审计;持有者心跳超时同样回收,实例被标记 OFFLINE。于是一条被遗弃的任务,恢复上限约是 min(锁窗口, 心跳超时) + 巡检间隔 ≈ 3 分钟——不是”等人类第二天上班发现”。

一个容易忽略的细节:过期回收把任务放回 Backlog,但不回滚已写的半成品。 重新认领的实例必须把任务当”可能做了一半”来对待,先验证再继续。无人值守系统里,失败语义写到这种程度,才够得上”确定”二字。

每一次 CLAIM / RELEASE / EXPIRE 都落进只追加(append-only)的审计日志——没有人盯着的管线,事后必须可追问。

机件二:中心推送的作业——Agent 不准自己排班

先讲翻车场景,规则的自然程度就显而易见了。假设让实例自己排”每日客户问题分诊”:

PM 角色今天跑了两个实例。A 实例分诊完,顺手安排了”明天同一时间再跑一次”;B 实例不知道 A 的安排,也给自己留了一份。明天,同一个客户 issue 被分诊两次,生成两倍的任务。

所以铁律是:一切周期性工作都是控制中心的一条 Job 记录,实例永远不自排、不自续。运行方式朴素到近乎无聊:

两个关键性质:实例是被动接收者——它从不轮询、不枚举、不认领任何排班;作业不进任务看板、不走认领锁——周期任务和一次性任务在模型上是两种东西,混在一起是大多数”定时 Agent”翻车的起点。日报周报类循环靠这个模型保证”每天恰好跑一次”,而不管底下开着一台还是十台工作机。

机件三:幂等契约——重跑不翻倍

无人值守意味着重跑是常态(续期失败、实例重启、看门狗回收后的再认领)。系统对”重复产出”的防线不是运气,而是一条全局约定:每个循环声明自己的去重键,动手之前先查重(check-before-create)。

环节 去重键 / 幂等机制
建任务 API 接受 idempotency_key,5 分钟窗口内重复请求返回缓存结果,绝不建出第二条
状态上报 同一转移在幂等窗口内重复上报安全无害
每日 PR 巡检 以 pr-reviewed 标签为去重键:查过的 PR 直接跳过
周期报告 文件名带 ISO 周编号(如 usage-report-2026-W36.md),同窗口重跑覆盖而非追加
发布 不可变的 v* tag 就是幂等键:有 tag 即已发,绝不二次发布
双周发布 先看 git diff <上个tag>..HEAD,零变更直接跳过并记审计

注意去重键的选择品位:优先用外部世界里本来就唯一的东西(标签、周编号、git tag、diff),而不是运行时再发明一个”已处理”状态表——前者天然可人工核对,后者又要有人(或另一个 Agent)来维护它的正确性。

三件机件在界面上是什么样

控制中心仪表盘把调度层的健康度直接摊开:在线实例数、本周完成、今日过期锁——过期锁这个小卡片,就是机件一的看门狗在工作留下的痕迹;按角色分布则暴露”哪个角色的队列堵了”:

控制中心仪表盘:任务按状态与角色分布、在线实例数、本周完成与今日过期锁


总结

  1. 认领锁 = 互斥 + 时限 + 兜底回收三件事的组合:原子性靠主键约束,活性靠续期与巡检,恢复语义靠”回 Backlog + 验证半成品”——缺一件都撑不起”无人值守”。
  2. 周期任务的所有权属于平台,这是多实例系统的第一定理:任何让 Agent 自己排班的设计,都会在角色扩容的那天变成重复产出器。
  3. 回去数一数你的系统里有几处”重跑会翻倍”的地方:每一处都应当有一个写得出来的去重键。写不出来,就是还没有真正的设计。

同系列上一篇《人不在环内:一个 AI 自主交付组织的概念契约》讲这套系统背后的概念契约,更早的《为什么要拆成三个仓库:知识、调度、档案的分离法则》讲分层背景——建议按顺序阅读。

📖 Read in English

← Back to all articles