Files
orbit/docs/sdk/worker.md

10 KiB
Raw Blame History

Go Worker 底层协议 SDK

更新:2026-09-17。已实现可独立构建的 WorkerConnection,配合真实 Orbit 服务和 MySQL 通过控制操作、WSS、ACK 丢失与租约过期回归。本页是底层接口,不自动启动处理器、心跳、续租、重连或排空;普通持续消费使用已实现的 自动 Worker 运行器。

源码:连接与控制方法、中文注释的数据契约。传输字段、权限和事务语义见 Worker 接口。SDK 独立 module,只依赖标准库和锁定版本的 github.com/coder/websocket v1.8.15,不导入服务端包。

1. 可运行的文本规范化示例

需要已完成迁移、已启动的 Orbit 和具有 workers:connect / workers:consume 权限的专用凭据。在仓库根目录运行:

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 sdk/go
ORBIT_URL=http://127.0.0.1:8080 \
  ORBIT_CREDENTIALS_FILE=../../.local/worker.credentials.json \
  go run ./examples/worker-protocol

预期输出:

task=<32位任务ID> final_state=SUCCEEDED attempts=1 result={"text":"HELLO ORBIT"}

完整源码 实际提交 {"text":" Hello Orbit "},领取并取得开始授权,执行去首尾空格/转大写,报告结果、查询当前状态并排空。没有生成文件或调用外部服务。每次运行创建独立任务与临时处理器名,避免消费前次示例遗留任务;历史和审计保留,不硬删除。

安全重试可通过 WorkerFinish.Retryable=true 声明明确失败允许再试,仅适用于 WorkerFailed;省略为 false。新尝试由服务端决定,详见 安全重试。

示例最多对开始/结果操作重连重报两次,保持原进程身份、尝试证明、消息 ID 和内容;业务函数只在首次取得有效开始确认后调用一次。网络结果仍不明时非零退出,不能推断服务端回滚。身份与待确认结果只保留在当前进程内,进程被终止后的未知结果由服务端回收进入核查。本示例处理很短的纯计算,不演示长任务自动续租。

2. 创建进程身份与连接

以下为接入函数内片段;完整错误处理见示例。

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. 当前授权与本地期限

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 表示当前窗口不允许开始;保留原消息身份在剩余租约内退避,不能调用业务处理器。队列控制、降额与窗口定义见 队列接口。
  • 传输中断返回 ErrWorkerDisconnected;消息格式/版本不兼容返回 ErrWorkerProtocol;主动关闭为 ErrWorkerClosed;超时/取消支持标准 context 错误。
  • 最多 32 个在途控制请求,达到上限返回 ErrWorkerBusy,请求未发送。同一消息 ID 已在途时也拒绝第二个并发等待者。
  • 派发缓冲最多 8 条;读取不及时会以 ErrWorkerBackpressure 关闭连接。持久化预占由服务端恢复,不无限积压内存。
  • 取消通知另有 32 条缓冲,满时同样关闭连接;NextCancellation 的等待 context 取消不会断开连接。重连后仍要通过 Resume/Renew 核对取消意图,通知不代替持久化状态。完整契约与先升级服务端的要求见 运行中取消。
  • 控制操作最长 10 秒,包含排队、发送与等待 ACK;调用方 context 可以缩短。已发送后取消/超时会关闭连接,操作是否提交需用原身份核对。
  • NextAssignment 的 context 取消只停止等待,不关闭连接。已关闭连接不会继续交付缓冲中的派发。
  • 入站单帧最多 512 KiB;读空闲期限 60 秒。需要调用方持续心跳,不能用此期限代替默认 45 秒会话期限。

以上是实现上限与正确性保护,不是容量承诺。测试命令与真实覆盖见 P2 SDK 协议回归。