feat: add automatic Worker runtime with lease recovery and bounded shutdown
This commit is contained in:
109
docs/sdk/worker-runtime.md
Normal file
109
docs/sdk/worker-runtime.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# 自动 Worker 接入
|
||||
|
||||
更新:2026-09-17。自动 Worker 已实现,并通过真实 MySQL、实际服务命令、WSS 断线/ACK 丢失/持续断网回归。P2 的尝试与证据查询、完整协议 Schema 和进程级故障验收仍需继续完成。
|
||||
|
||||
源码:[运行器](../../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 快照,不是持久化业务成功率。
|
||||
|
||||
`Events()` 是 128 条缓冲的只读本地通知流,不读取也不会阻塞运行;缓冲满时丢弃并计数,Run 返回时关闭。通知只含类型、任务/尝试 ID 与稳定分类,没有业务结果或秘密。它不能代替审计、可靠结果订阅或任务查询。
|
||||
|
||||
## 4. 执行、续租与恢复
|
||||
|
||||
- 只有 Start 当前授权有效才调用处理器;同进程中的已知尝试最多调用一次。重复确认与重新连接只核对控制操作。
|
||||
- 处理器在独立 goroutine 执行,网络控制由事件循环管理;每连接当前只保留一个执行器控制请求在途。不同处理器可以并行,控制请求顺序与业务并发上限分开。
|
||||
- 会话心跳按服务端 TTL 的三分之一计算,上限 5 秒。任务续租按当前保守租约剩余时间的三分之一安排,下限 20 毫秒;服务器仍限制总截止与单次尝试时限。
|
||||
- 每个处理器的租约监视器独立取消 context,断网或控制请求阻塞也不会延长本地执行权限。最新会话若缩短租约,监视器会采用较短期限。已到期的处理器不会被后续确认复活。
|
||||
- 重连复用实例和尝试身份,在开启容量之前 Resume 所有已知尝试;待确认 start/renew/finish 使用原消息 ID。认证、权限及协议错误终止 Run;临时服务/连接错误按退避核对。
|
||||
- 结果未确认或处理器失去租约时,运行器降低对外声明的容量。仍未返回的函数占用真实并发槽;已在传输中的预占会有界接收,已知尝试最多为配置容量的两倍。
|
||||
|
||||
Run 的重连是控制消息恢复,不是业务自动重试。当前明确失败仍成为 FAILED;策略化业务重试在 P3 实现。
|
||||
|
||||
## 5. 结果与错误分类
|
||||
|
||||
| 处理器结果 | SDK 报告 / 服务端行为 |
|
||||
| --- | --- |
|
||||
| 合法非 null JSON + nil error | SUCCEEDED |
|
||||
| `&orbit.WorkerFailure{Code:"OBJECT_REJECTED", Details:...}` | FAILED;仅用于能确认的业务失败,Details 为空时为 `{}` |
|
||||
| 普通 error | UNKNOWN / HANDLER_ERROR,进入 NEEDS_REVIEW |
|
||||
| panic / runtime.Goexit | UNKNOWN / HANDLER_PANIC 或 HANDLER_ABORTED |
|
||||
| 非法 JSON、超限结果、不支持数值或嵌套 | UNKNOWN / INVALID_HANDLER_RESULT |
|
||||
| 处理器因租约取消返回错误 | UNKNOWN / LEASE_LOST |
|
||||
| 报告到达时租约已失效、或结果与权威状态冲突 | 服务端可只保存 EVIDENCE_ONLY;不把证据计为任务成功 |
|
||||
|
||||
原始异常和 panic 内容不写入结果或通知。Result/Details 最多 256 KiB,应优先使用对象引用。SDK 保留精确数字文本,并按服务端数字范围及完整帧的嵌套上限预检,避免产生永远无法接受的报告。
|
||||
|
||||
普通网络错误不能证明副作用未发生,因此不会自动解释为明确失败。UNKNOWN 和迟到证据的后续核查由平台管理能力与业务对账共同处理。
|
||||
|
||||
## 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)。实际部署中的多主机故障、数据库复制确认与容量承诺仍需后续生产验收。
|
||||
Reference in New Issue
Block a user