Files
orbit/docs/sdk/worker.md

130 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Go Worker 底层协议 SDK
更新:2026-09-17。已实现可独立构建的 `WorkerConnection`,配合真实 Orbit 服务和 MySQL 通过控制操作、WSS、ACK 丢失与租约过期回归。本页是底层接口,不自动启动处理器、心跳、续租、重连或排空;普通持续消费使用已实现的 [自动 Worker 运行器](worker-runtime.md)。
源码:[连接与控制方法](../../sdk/go/worker_connection.go)、[中文注释的数据契约](../../sdk/go/worker_types.go)。传输字段、权限和事务语义见 [Worker 接口](../api/worker.md)。SDK 独立 module,只依赖标准库和锁定版本的 `github.com/coder/websocket v1.8.15`,不导入服务端包。
## 1. 可运行的文本规范化示例
需要已完成迁移、已启动的 Orbit 和具有 `workers:connect` / `workers:consume` 权限的专用凭据。在仓库根目录运行:
```sh
mkdir -p .local
chmod 700 .local
go run ./cmd/orbit-admin bootstrap \
--application worker-example --namespace default --environment development \
--queue normalization --worker-access --output .local/worker.credentials.json
cd examples
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=../.local/worker.credentials.json \
go run ./worker-protocol
```
预期输出:
```text
task=<32位任务ID> final_state=SUCCEEDED attempts=1 result={"text":"HELLO ORBIT"}
```
[完整源码](../../examples/worker-protocol/main.go) 实际提交 `{"text":" Hello Orbit "}`,领取并取得开始授权,执行去首尾空格/转大写,报告结果、查询当前状态并排空。没有生成文件或调用外部服务。每次运行创建独立任务与临时处理器名,避免消费前次示例遗留任务;历史和审计保留,不硬删除。
安全重试可通过 `WorkerFinish.Retryable=true` 声明明确失败允许再试,仅适用于 WorkerFailed;省略为 false。新尝试由服务端决定,详见 [安全重试](../api/retries.md)。
示例最多对开始/结果操作重连重报两次,保持原进程身份、尝试证明、消息 ID 和内容;业务函数只在首次取得有效开始确认后调用一次。网络结果仍不明时非零退出,不能推断服务端回滚。身份与待确认结果只保留在当前进程内,进程被终止后的未知结果由服务端回收进入核查。本示例处理很短的纯计算,不演示长任务自动续租。
## 2. 创建进程身份与连接
以下为接入函数内片段;完整错误处理见示例。
```go
registration, err := orbit.NewWorkerRegistration(queueID, 4, []orbit.WorkerCapability{
{Handler: "text.normalize", HandlerVersion: "v1", PayloadVersion: "v1"},
})
if err != nil { return err }
conn, err := client.DialWorker(ctx, registration)
if err != nil { return err }
defer conn.Close()
```
同一进程只生成一次 registration;重连复用,进程重启换新值。它固定凭据、队列、能力集合和实例秘密。不要把身份和秘密复制给另一个活跃进程:新会话会隔离旧会话。
`DialWorker` 的 ctx 只控制握手,最长 8 秒,调用方配置的 HTTP 超时可以更短;成功返回后需主动 Close。URL 基于 `NewClient` 的 HTTP(S) 地址,分别建立 WS/WSS,禁止重定向,不关闭 TLS 证书校验,不把 token 放入 URL。只支持 `pulse.worker.v1` 子协议,禁用压缩。
`Welcome()` 返回首次连接快照;`Done()` 关闭表示读取循环已退出;`Err()` 返回关闭原因。`Close()` 立即停止传输,不撤销外部已发出的请求,也不代替排空确认。
注册和尝试证明包含秘密,JSON 序列化只用于协议。SDK 的 `String` / `GoString` 隐藏注册、尝试、派发和结果中的秘密/业务数据;直接读字段、JSON 日志和调试器仍可能泄露,接入方应只记录任务 ID、尝试 ID 和错误分类。
## 3. 方法清单
除 Welcome/Done/Err/Close,所有连接操作第一个参数都是 context。控制操作同时要求调用方提供 32 位小写十六进制 message ID。
| 方法 | 输入 | 返回 | 注意事项 |
| --- | --- | --- | --- |
| `NewWorkerRegistration` | queueID、capacity、capabilities | WorkerRegistration | 生成随机进程身份与秘密,复制/排序能力,拒绝重复能力 |
| `NewWorkerMessageID` | 无 | string | 只为新的逻辑操作生成;未知结果重报复用旧值 |
| `client.DialWorker` | registration | *WorkerConnection | 握手后尚未启用派发 |
| `conn.Capacity` | messageID、WorkerCapacity | WorkerWelcome | 总容量覆盖值;同时续期会话 |
| `conn.NextAssignment` | ctx | WorkerAssignment | 只表示预占,不能直接运行处理器 |
| `conn.NextCancellation` | ctx | WorkerCancellation | 注册 CancellationNotifications=true 后持续读取,只停止匹配 task/attempt/epoch 的处理器 |
| `conn.Start` | messageID、WorkerAttempt | WorkerCommandResult | 持久化开始后确认,检查 Lease 的当前授权 |
| `conn.Renew` | messageID、WorkerAttempt | WorkerCommandResult | 新续租用新消息;原消息只核对原操作 |
| `conn.Finish` | messageID、WorkerFinish | WorkerCommandResult | 区分权威结果 APPLIED 与证据 EVIDENCE_ONLY |
| `conn.Resume` | messageID、WorkerAttempt | WorkerLease | 重连核对原尝试,不创建尝试、不延长租约 |
同连接内 `Capacity.Sequence` 从 1 开始递增;同序号必须同容量/排空标志,不能用增量值。容量为零停止新领取,不释放在途许可。首次确认容量才开启派发,所以重连时可以先 Resume 所有已知尝试,再发布容量。会话与任务续期是两件事:Capacity 不延长任务租约,Renew 不代替会话心跳。
排空使用 `Drain:true`。排空在同进程重连后保留,后续心跳也必须带 true;尝试清除会返回 `STATE_CONFLICT`。排空不禁止收集已发请求结果。
## 4. 当前授权与本地期限
```go
assignment, err := conn.NextAssignment(ctx)
if err != nil { return err }
ref := assignment.Attempt()
startID, err := orbit.NewWorkerMessageID()
if err != nil { return err }
started, err := conn.Start(ctx, startID, ref)
if err != nil { return err }
deadline, active := started.Lease.ExecutionDeadline()
if !active { return errors.New("attempt has no current execution authority") }
work, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
// 将 work 与业务输入传给处理器,不传执行秘密。
```
`ExecutionDeadline()` 只认可 `Active=true`、`State=RUNNING`、CancelRequestedAt 为空和仍未到期的本地期限。取消后 Active 仍可为 true,用于报告当前结果,不授予继续执行权。SDK 以服务端 `lease_until-server_time` 为窗口,扣除从调用开始起的全部耗时(含等待写入)及 250 毫秒安全余量,用本地单调时钟表示。数据库墙钟与客户端相差一天也不会额外获得一天授权。
只有 SDK 控制响应会设置内部本地期限;自行构造/JSON 解码的 WorkerLease 不会得到执行授权。旧 start 回执即使包含历史 RUNNING 和未来 lease_until,也不能越过当前 Lease 的失效判断。
此底层接口不会替调用方更新处理器上下文。长任务必须有独立的续租与到期取消机制,断线期间也要在最后确认的本地期限停止推进。新的 Resume/Start 回执不能复活一个已因租约到期而停止的处理器。Go 无法强杀不理会 context 的 goroutine,SDK 更不能撤回外部请求;业务幂等/执行代次隔离仍需在接入方落实。
## 5. 重报与结果分类
| 情况 | 正确处理 |
| --- | --- |
| Start 请求结果不明,业务尚未开始 | 保留 startID;重连后 Resume,再用原 startID 核对;核验当前授权后最多开始一次 |
| 处理器已在当前进程运行,随后断线 | 不再次调用处理器;保留它的已确认租约期限,重连核对原尝试 |
| Finish 请求结果不明 | 保留原 finishID、Outcome、Result、ErrorCode,重连核对后原样重报 |
| 明确业务成功 | `WorkerSucceeded`,不带 ErrorCode |
| 可确认的业务失败 | `WorkerFailed`,带稳定 ErrorCode |
| 收到取消意图,确认已停止且本次无未结清效果 | `WorkerCancelled`,带稳定 ErrorCode;普通 context 错误不足以证明 |
| 超时、失联或不能确认外部效果 | `WorkerUnknown`,带稳定 ErrorCode;默认转核查,仅 IDEMPOTENT 且预算允许时自动重试 |
| `Disposition=EVIDENCE_ONLY` | 内容已留作证据;不能当作任务成功,另查 GetTask 的权威状态 |
Result 是不超过 256 KiB 的非 null JSON,可传对象引用;原始异常不应写入稳定错误分类。SDK 拒绝非法 Unicode、重复键和过深 JSON,保留数字文本;数值范围仍由服务端最终校验。
## 6. 错误与有界行为
- 握手 HTTP 拒绝返回 `*HTTPError`;业务控制拒绝返回 `*WorkerError`,通过 Code 分支处理。Error() 只显示分类,避免未知错误正文进入日志。
- Start 的 RATE_LIMITED 表示当前窗口不允许开始;保留原消息身份在剩余租约内退避,不能调用业务处理器。队列控制、降额与窗口定义见 [队列接口](../api/queues.md)。
- 传输中断返回 `ErrWorkerDisconnected`;消息格式/版本不兼容返回 `ErrWorkerProtocol`;主动关闭为 `ErrWorkerClosed`;超时/取消支持标准 context 错误。
- 最多 32 个在途控制请求,达到上限返回 `ErrWorkerBusy`,请求未发送。同一消息 ID 已在途时也拒绝第二个并发等待者。
- 派发缓冲最多 8 条;读取不及时会以 `ErrWorkerBackpressure` 关闭连接。持久化预占由服务端恢复,不无限积压内存。
- 取消通知另有 32 条缓冲,满时同样关闭连接;NextCancellation 的等待 context 取消不会断开连接。重连后仍要通过 Resume/Renew 核对取消意图,通知不代替持久化状态。完整契约与先升级服务端的要求见 [运行中取消](../api/cancellation.md)。
- 控制操作最长 10 秒,包含排队、发送与等待 ACK;调用方 context 可以缩短。已发送后取消/超时会关闭连接,操作是否提交需用原身份核对。
- `NextAssignment` 的 context 取消只停止等待,不关闭连接。已关闭连接不会继续交付缓冲中的派发。
- 入站单帧最多 512 KiB;读空闲期限 60 秒。需要调用方持续心跳,不能用此期限代替默认 45 秒会话期限。
以上是实现上限与正确性保护,不是容量承诺。测试命令与真实覆盖见 [P2 SDK 协议回归](../reports/phase-2-sdk-protocol.md)。