Files
orbit/docs/sdk/worker-runtime.md

117 lines
10 KiB
Markdown
Raw 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.
# 自动 Worker 接入
更新:2026-09-17。自动 Worker 已实现,并通过真实 MySQL、实际服务命令、WSS 断线/ACK 丢失/持续断网及 [进程强杀恢复回归](../reports/phase-2.md)。[尝试与证据查询](../api/execution-history.md) 可核对结果,协议形状见 [Schema](../api/worker-schema.md)。
源码:[运行器](../../sdk/go/worker_runtime.go)、[配置与中文注释](../../sdk/go/worker_runtime_types.go)、[完整示例](../../sdk/go/examples/worker/main.go)。特殊传输集成可以使用 [底层协议 SDK](worker.md),普通消费直接使用本页的 Worker。
## 1. 运行完整示例
按 [底层 SDK 的初始化步骤](worker.md#1-可运行的文本规范化示例) 创建具有 Worker 权限的专用凭据,在仓库根目录执行:
```sh
cd sdk/go
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=../../.local/worker.credentials.json \
go run ./examples/worker
```
示例提交三条文本任务,以容量 2 启动自动 Worker,真实执行文本规范化,查询结果后取消 Run context 并等待排空。预期输出:
```text
task=<任务ID> state=SUCCEEDED result={"text":"HELLO ORBIT"}
task=<任务ID> state=SUCCEEDED result={"text":"REUSABLE WORKER"}
task=<任务ID> state=SUCCEEDED result={"text":"DURABLE RESULT"}
worker_drained=true started=3 confirmed=3 running=0
```
每次示例使用独立操作和能力名称,避免接走以前运行留下的任务;任务与审计历史保留。生产通常使用固定处理器版本,由独立长驻 Worker 消费;本示例把生产者和消费者放在一个命令里方便复现。
## 2. 注册处理器与启动
以下为接入函数片段;函数收到的 ctx 应来自进程信号或应用生命周期,并等待 Run 返回后再退出进程。
```go
worker, err := orbit.NewWorker(client, orbit.WorkerOptions{
QueueID: queueID,
Capacity: 4,
ShutdownTimeout: 30 * time.Second,
Handlers: []orbit.WorkerHandler{{
Capability: orbit.WorkerCapability{
Handler: "text.normalize", HandlerVersion: "v1", PayloadVersion: "v1",
},
Handle: func(ctx context.Context, execution orbit.WorkerExecution) (json.RawMessage, error) {
// 实际 HTTP/数据库/文件操作应使用这个 ctx,并实现业务效果幂等。
if err := ctx.Err(); err != nil { return nil, err }
return json.RawMessage(`{"processed":true}`), nil
},
}},
})
if err != nil { return err }
return worker.Run(ctx)
```
任务的 handler、handler_version、payload_version 必须和注册能力精确匹配。处理器收到 Task、AttemptID 与执行 Epoch,不收到实例/尝试秘密。Task.ID 或接入方保存的业务操作键用于效果去重;AttemptID 标识一次尝试,不能作为跨尝试的业务幂等键。资源侧支持执行代次校验时,可拒绝旧 Epoch 的写入。
SDK 的 Run 每个实例只能调用一次,包括已经正常退出的实例。重连由内部保留原身份处理;不要通过反复 NewWorker 来代替断线恢复。进程重启会生成新身份,不能继续使用旧进程的内存状态来声称已经完成。
## 3. 配置和接口
| 配置 | 默认 / 范围 | 含义 |
| --- | --- | --- |
| QueueID | 必填,32 位 ID | 当前凭据有消费权限的队列 |
| Capacity | 4;1–256 | 同时尚未返回的处理器上限;不响应取消的函数仍占物理槽 |
| Handlers | 1–32 项 | 精确能力,重复能力和空函数在构造时拒绝 |
| ShutdownTimeout | 30 秒;1 毫秒–5 分钟 | 从 Run context 取消起计算的整体收尾期限 |
| ReconnectMinDelay | 200 毫秒;至少 10 毫秒 | 连接失败退避起点 |
| ReconnectMaxDelay | 5 秒;不小于起点,最多 30 秒 | 指数退避上限,区间抖动;不增加任务尝试次数 |
`Run(ctx)` 正常排空返回 nil;同实例再次运行返回 ErrWorkerAlreadyRun。`Stats()` 返回进程内 Running、Started、RenewalsConfirmed、ReportsConfirmed、EvidenceConfirmed、Reconnects、DroppedEvents 快照,不是持久化业务成功率。ReportsConfirmed 包含已确认的失败/未知尝试报告,一条重试任务可能计多次。
`Events()` 是 128 条缓冲的只读本地通知流,不读取也不会阻塞运行;缓冲满时丢弃并计数,Run 返回时关闭。通知只含类型、任务/尝试 ID 与稳定分类,没有业务结果或秘密。它不能代替审计、可靠结果订阅或任务查询。
## 4. 执行、续租与恢复
- 只有 Start 当前授权有效才调用处理器;同进程中的已知尝试最多调用一次。重复确认与重新连接只核对控制操作。
- 自动 Worker 开启取消通知,收到匹配尝试的通知或 Resume/Start/Renew 返回取消意图后,立即取消处理器 context,原因是 ErrTaskCancellationRequested,停止续租并继续上报真实结果。该函数未返回前仍占本地物理槽。
- 处理器在独立 goroutine 执行,网络控制由事件循环管理;每连接当前只保留一个执行器控制请求在途。不同处理器可以并行,控制请求顺序与业务并发上限分开。
- 会话心跳按服务端 TTL 的三分之一计算,上限 5 秒。任务续租按当前保守租约剩余时间的三分之一安排,下限 20 毫秒;服务器仍限制总截止与单次尝试时限。
- 每个处理器的租约监视器独立取消 context,断网或控制请求阻塞也不会延长本地执行权限。最新会话若缩短租约,监视器会采用较短期限。已到期的处理器不会被后续确认复活。
- 重连复用实例和尝试身份,在开启容量之前 Resume 所有已知尝试;待确认 start/renew/finish 使用原消息 ID。认证、权限及协议错误终止 Run;临时服务/连接错误按退避核对。
- 结果未确认或处理器失去租约时,运行器降低对外声明的容量。仍未返回的函数占用真实并发槽;已在传输中的预占会有界接收,已知尝试最多为配置容量的两倍。
Run 的重连恢复同一次控制操作。平台的 [安全重试](../api/retries.md) 使用同一 Task.ID 派发新的 AttemptID/Epoch,运行器按新的有效授权调用处理器,预算与退避由服务端持久化管理。
Start 返回 EXECUTION_PAUSED 或 RATE_LIMITED 时,运行器保留原 start 消息身份退避核对,不调用处理器;有限授权到期后按服务端恢复规则处理。新领取已受 [共享队列额度](../api/queues.md) 约束,正常等待限流不消耗新的尝试预算。配置降额不意味着已在执行的 goroutine 已停止。
## 5. 结果与错误分类
| 处理器结果 | SDK 报告 / 服务端行为 |
| --- | --- |
| 合法非 null JSON + nil error | SUCCEEDED |
| `&orbit.WorkerFailure{Code:"OBJECT_REJECTED", Details:...}` | FAILED;默认 Retryable=false,Details 为空时为 `{}` |
| `&orbit.WorkerFailure{Code:"TEMPORARY_REJECTION", Retryable:true}` | 明确失败可安全重试,平台仍核验次数、总时限和历史不确定效果 |
| 普通 error | UNKNOWN / HANDLER_ERROR;默认转核查,仅 IDEMPOTENT 且预算允许时可自动重试 |
| panic / runtime.Goexit | UNKNOWN / HANDLER_PANIC 或 HANDLER_ABORTED |
| 非法 JSON、超限结果、不支持数值或嵌套 | UNKNOWN / INVALID_HANDLER_RESULT |
| 处理器因租约取消返回错误 | UNKNOWN / LEASE_LOST |
| 收到任务取消后返回 `&orbit.WorkerCancellationConfirmed{Details:...}` | CANCELLED;只用于确认本次停止且无未结清效果,历史未知效果仍需核查 |
| 报告到达时租约已失效、或结果与权威状态冲突 | 服务端可只保存 EVIDENCE_ONLY;不把证据计为任务成功 |
原始异常和 panic 内容不写入结果或通知。Result/Details 最多 256 KiB,应优先使用对象引用。SDK 保留精确数字文本,并按服务端数字范围及完整帧的嵌套上限预检,避免产生永远无法接受的报告。
普通网络错误不能证明副作用未发生,因此不会自动解释为明确失败。UNKNOWN 由服务端结合真实幂等声明决定是否重试;迟到证据不能清除历史不确定效果。完整规则及可运行示例见 [安全重试](../api/retries.md)。
任务取消也不能自动解释为已停止。普通 ctx.Err() 仍报告 UNKNOWN;只有业务明确确认后才返回 WorkerCancellationConfirmed。已经成功时返回真实成功结果,不能因为 context 同时取消而覆盖成功。开始 ACK 丢失且处理器尚未调用时,SDK 重连核对意图后可以报告本次未执行;用法、升级顺序和完整示例见 [取消契约](../api/cancellation.md)。
## 6. 退出与故障边界
取消 Run context 后,运行器请求持久化排空,收集排空 ACK 之前已派发的工作,继续为在途任务续租并确认结果。进程应等待 Run 返回,而不是取消 context 后立即退出。
ShutdownTimeout 从取消信号发生时开始计时,包含网络等待和处理器收尾。到期后取消控制请求及处理器 context,返回 `*WorkerRunError`;可用 `errors.Is(err, orbit.ErrWorkerShutdownTimeout)` 判断,Unconfirmed 和 Running 给出终止收尾时的快照。其他致命错误也带这些计数,便于识别未核对的执行。
Go 无法杀死忽略 context 的 goroutine。Run 可以有界返回,但这类函数可能仍在进程中执行;不得把返回错误解释为副作用已停止,也不要在同一进程盲目重建 Worker 叠加执行。服务端对失联执行按租约回收,依冻结安全策略决定重试或进入核查。
身份、尚未确认的结果和消息 ID 当前保存在内存,支持同进程重连;进程被强杀会失去这些内容。持久化租约、操作回执、最终结果和审计在 MySQL 中,业务外部效果仍需要业务幂等或查询核对。这一边界不等于端到端 exactly-once。
运行器细节见 [自动运行器回归报告](../reports/phase-2-worker-runtime.md),后续真实进程故障证据见 [P2 阶段验收](../reports/phase-2.md)。实际部署中的多主机故障、数据库复制确认与容量承诺仍需后续生产验收。