152 lines
11 KiB
Markdown
152 lines
11 KiB
Markdown
# Worker 通信与执行契约 v1
|
||
|
||
已实现服务端执行事务、WebSocket 接入、[独立 Go 底层协议 SDK](../sdk/worker.md)、[自动 Worker 运行器](../sdk/worker-runtime.md) 与 [尝试/证据查询](execution-history.md),通过 [P2 阶段验收](../reports/phase-2.md),包含真实 WSS、协议 Schema、自动续租、ACK 丢失/断网恢复及进程强杀后的节点接管。底层 WorkerConnection 不自动管理处理器;NewWorker / Run 提供自动管理。
|
||
|
||
## 1. 接入与身份
|
||
|
||
入口 `GET /v1/worker/connect`,子协议必须为 `pulse.worker.v1`。Authorization 使用 Bearer 凭据;禁止 URL 查询参数。握手先校验 workers:connect,消费控制操作再次校验 workers:consume、队列范围和凭据有效性。
|
||
|
||
初始化开发凭据可显式增加 Worker 权限:
|
||
|
||
```sh
|
||
go run ./cmd/orbit-admin bootstrap \
|
||
--application worker-demo --namespace default --environment development \
|
||
--queue objects --worker-access --output .local/worker.credentials.json
|
||
```
|
||
|
||
默认 bootstrap 仍只有 P1 的五项权限,现有凭据不会自动扩权。生产细粒度凭据管理在 P6 完成。
|
||
|
||
当前 orbit 命令监听 HTTP,开发可使用 ws;生产需要受控的 TLS 终止。WSS 回归使用真实 TLS 测试服务器及受信任测试证书,没有跳过证书校验。传输库锁定 github.com/coder/websocket v1.8.15,使用上下文、读写期限、子协议和默认 Origin 校验,关闭压缩。[上游版本记录](https://github.com/coder/websocket/releases/tag/v1.8.15)、[上游接口文档](https://pkg.go.dev/github.com/coder/websocket@v1.8.15)。
|
||
|
||
## 2. 帧格式
|
||
|
||
只接受 JSON 文本帧;单帧最多 512 KiB,业务结果最多 256 KiB。重复键、未知字段、无效 Unicode 和非法数值按 HTTP 相同规则拒绝。
|
||
|
||
机器可读契约及各方向定义见 [JSON Schema 说明](worker-schema.md)。控制字段必须显式提供;epoch/sequence 等计数使用无前导零的规范十进制字符串。请求禁止响应字段,空字符串/null 也不例外。
|
||
|
||
```json
|
||
{
|
||
"protocol_version":"1",
|
||
"type":"task.start",
|
||
"message_id":"44444444444444444444444444444444",
|
||
"session_id":"33333333333333333333333333333333",
|
||
"body":{
|
||
"task_id":"11111111111111111111111111111111",
|
||
"attempt_id":"22222222222222222222222222222222",
|
||
"epoch":"1",
|
||
"lease_secret":"<执行秘密,仅通过受控连接传输>"
|
||
}
|
||
}
|
||
```
|
||
|
||
所有 ID 为 32 位小写十六进制;epoch、sequence、revision 用十进制字符串。message_id 标识一次操作,重连重报保持不变;session_id 必须换为当前连接身份。服务端响应以 reply_to 关联请求:
|
||
|
||
```json
|
||
{
|
||
"protocol_version":"1",
|
||
"type":"error",
|
||
"message_id":"55555555555555555555555555555555",
|
||
"reply_to":"44444444444444444444444444444444",
|
||
"session_id":"33333333333333333333333333333333",
|
||
"error":{"code":"LEASE_LOST","message":"attempt no longer grants execution authority"}
|
||
}
|
||
```
|
||
|
||
普通成功响应 type=ack,body 为下表对应结果。服务端主动派发 type=task.assign,无 reply_to。错误仅包含稳定分类和公开消息,不包含 SQL、业务参数或秘密。
|
||
|
||
## 3. 注册、恢复与容量
|
||
|
||
连接后 5 秒内必须发送 worker.hello,hello 不携带 session_id。
|
||
|
||
| hello 字段 | 含义 |
|
||
| --- | --- |
|
||
| instance_id | Worker 进程实例 ID;进程重启必须换新值 |
|
||
| instance_secret | 32 字节随机秘密的无填充 base64url 编码,共 43 字符;同进程重连保留 |
|
||
| queue_id | 当前进程绑定队列 |
|
||
| capabilities | 1–32 个 handler / handler_version / payload_version 精确版本组合,不可重复 |
|
||
| capacity | 总执行槽数量,1–256,不是可用槽的累加值 |
|
||
| cancellation_notifications | 可选 bool,默认 false;true 接收 task.cancel 通知,自动 Worker 默认开启 |
|
||
|
||
重连必须保持凭据、队列、处理器集合和进程秘密,处理器集合的排列顺序不影响身份。新连接递增会话代次并使旧会话失效。知道 instance_id 或 task_id 不能接管执行。
|
||
|
||
worker.welcome 返回 session_id、epoch、server_time、expires_at、lease_duration_ms、draining。当前窗口为会话 45 秒、执行租约最多 30 秒;注册及能力声明不等于开始授权。
|
||
|
||
**派发在收到第一次 worker.capacity 确认后才启用**,便于客户端先恢复旧尝试。
|
||
|
||
| 类型 | body | 返回 |
|
||
| --- | --- | --- |
|
||
| worker.resume | AttemptRef,字段见上方 task.start 示例 | 当前 LeaseStatus;成功迁移有效尝试到新会话,不创建尝试或延长期限 |
|
||
| worker.capacity | sequence 字符串、capacity 0–256、drain bool | 更新后的 welcome 字段 |
|
||
| task.assign | 服务端主动发送 | task 完整任务、attempt_id、epoch、lease_secret、lease_until、server_time |
|
||
|
||
capacity 使用单调递增序号和覆盖值。同序号同内容可重复,同序号不同内容或旧序号冲突;capacity=0 不释放已有任务的并发许可。drain=true 是进程级持久化排空,重连保留;后续必须保持 drain=true,尝试清除返回 STATE_CONFLICT。新进程使用新实例身份。
|
||
|
||
一个 Worker 进程当前绑定一个队列。可以运行多个 Worker 进程或后续 SDK 实例消费不同队列;不能通过重连静默切换队列。
|
||
|
||
## 4. 开始、续租与结果
|
||
|
||
| 类型 | body | 语义 |
|
||
| --- | --- | --- |
|
||
| task.start | AttemptRef | 领取后再次检查当前会话、尝试、租约、队列暂停及流程门控,保存 RUNNING 后确认 |
|
||
| task.renew | AttemptRef | 只续期当前 RUNNING;新消息可续租,重复原消息只取回原回执 |
|
||
| task.finish | ref: AttemptRef、outcome、result、error_code、可选 retryable | 保存明确结果或未知效果;返回操作回执和当前授权 |
|
||
|
||
outcome 为 SUCCEEDED / FAILED / UNKNOWN / CANCELLED。result 为非 null JSON 或证据引用。非成功结果必须带 1–64 字符的稳定 error_code;SUCCEEDED 不带错误分类。retryable 省略为 false,只有 FAILED 可设 true。CANCELLED 要求已保存取消意图且确认本次停止,不能用普通 context 错误推断;协议与升级顺序见 [取消契约](cancellation.md)。FAILED/true 受次数与时限限制进入退避;UNKNOWN 仅在冻结策略为 IDEMPOTENT、未取消且预算允许时自动恢复,其他情况转 NEEDS_REVIEW。历史不确定效果不会被后续失败或取消覆盖;完整规则见 [安全重试](retries.md)。
|
||
|
||
开始、续租和结束返回 CommandResult:
|
||
|
||
```json
|
||
{
|
||
"receipt":{
|
||
"operation":"start",
|
||
"task_state":"RUNNING",
|
||
"attempt_state":"RUNNING",
|
||
"task_revision":"3",
|
||
"lease_until":"2026-09-17T08:00:30Z",
|
||
"disposition":"APPLIED"
|
||
},
|
||
"lease":{
|
||
"active":true,
|
||
"state":"RUNNING",
|
||
"lease_until":"2026-09-17T08:00:30Z",
|
||
"server_time":"2026-09-17T08:00:00Z"
|
||
},
|
||
"deduplicated":false
|
||
}
|
||
```
|
||
|
||
receipt 是原操作的不可变确认;lease 是本次重新核验的执行权,两者不能混用:
|
||
|
||
- 只有 lease.active=true、lease.state=RUNNING、没有 cancel_requested_at,且仍在保守的本地有效期内,才允许处理器推进。LEASED 的 active 只表示预占有效。取消后的 active 可为 true,用于确认当前结果;不允许继续业务执行或延长期限。
|
||
- 原 start 回执可能仍表示 RUNNING,但当前 lease.active=false;旧 ACK 不能让已过期、已结束或已被替换的尝试复活。
|
||
- 有效期不超过租约窗口、会话期限、任务总截止和从 authorized_at 起计算的单次尝试时限。
|
||
- 客户端需要扣除请求总耗时及安全余量,用本地单调时钟停止执行,不能假定本地墙上时钟与数据库一致。
|
||
- 同结果重报不重复释放许可或产生成功事件;即使换 message_id,也返回该结果第一次已应用的回执,不借用后续尝试的状态。同消息改变内容或 retryable 返回 IDEMPOTENCY_CONFLICT。
|
||
- 迟到、未开始或与已保存结果冲突的内容,经过身份和秘密验证后保存为 EVIDENCE_ONLY;不改变任务权威结果,不当作新的执行授权。
|
||
- 断线后原结果仍保留稳定消息 ID,可在新会话恢复/核对后重报。不会因重连创建第二次业务执行。
|
||
|
||
## 5. 领取、并发与回收
|
||
|
||
平台在同一事务中分配队列并发许可、创建尝试、递增任务执行代次和次数、保存 LEASED 与派发事件。领取本身消耗一次 max_attempts 预算,包括最终未获开始授权的尝试,预算不会因重连或过期恢复重置。
|
||
|
||
运行中的进程总容量跨其所有旧/新会话统计。声明容量减少、断开连接或重复结果,都不能重复释放额度。已实施队列 max_concurrency、rate_per_second 及未终结任务上限;参数含义、窗口突发、暂停及降额语义见 [队列资源控制](queues.md)。应用级配额、业务键许可与公平性仍在 P3 推进。
|
||
|
||
领取前核对速率窗口及已有 LEASED 预留,满额时不新建尝试。Start 在同一队列事务内消费开始预算,原 ACK 重报不重复计数。领取后若配置降额,Start 可能返回 RATE_LIMITED;保持原消息身份在剩余租约内退避核对。续租、结果确认、取消及恢复不消费新开始额度。
|
||
|
||
每个节点每 2 秒发起一批恢复扫描,每批最多 100 项:
|
||
|
||
- 未批准开始即到期:有剩余次数和截止时间时按冻结策略退避;否则无历史不确定效果为 FAILED,有未核实效果为 NEEDS_REVIEW。
|
||
- 已批准开始后失联:尝试 UNKNOWN;RECONCILE_ONLY 转 NEEDS_REVIEW,IDEMPOTENT 仅在次数及时限允许时退避重试,并保留 effect_uncertain。
|
||
- WAITING 超过总截止或最晚开始时间:无历史不确定效果为 FAILED,否则 NEEDS_REVIEW。
|
||
- 回收再次锁定队列、任务、尝试并读取数据库时间,与续租/完成串行化;多个节点不能重复释放许可或重复终结事件。
|
||
|
||
安全重试已使用同一决策逻辑处理有效结果与租约回收;下一执行时间持久化在任务中,重启不丢失等待。当前不提供人工重试或核查解除接口。
|
||
|
||
## 6. 连接保护与当前边界
|
||
|
||
当前节点上限 128 条 Worker 连接;每连接入站队列 4 帧、出站队列 8 帧,派发保留 ACK 缓冲空间,每秒最多发起 4 次新派发尝试。读空闲期限 60 秒、写期限 5 秒、单个数据库控制操作期限 8 秒。慢消费者导致连接关闭时,已提交状态仍由稳定身份恢复。
|
||
|
||
这些是有界的开发基线,不代表经过容量压测的生产参数。当前同队列操作使用较粗的行锁协调,吞吐与公平性需要 P6 按负载验证。服务退出会显式关闭被 HTTP Upgrade 接管的连接;断开不会直接撤销外部已发出的请求。
|
||
|
||
协议 Schema 和真实进程强杀恢复已通过回归,见 [P2 阶段验收](../reports/phase-2.md)。[运行中取消](cancellation.md) 已实现并通过回归;凭据轮换、数据库故障切换、跨主机演练与容量验收属于后续生产验证。自动运行器对非协作处理器提供有界退出与状态报告,不能强制停止其外部副作用。
|