feat: add bounded polling and evidence-based workflow recovery examples
This commit is contained in:
@@ -14,6 +14,10 @@
|
||||
- 两个任务并行处理并汇合,计时等待后接收显式目标代次的信号,再执行后续任务;最终三个任务完成并有界排空。
|
||||
- 启动及信号原键重复不会建立新的实例或信号;分页查看原状态事件,核对信号消费及赢家等待。
|
||||
|
||||
## 已可运行:有界轮询与证据恢复
|
||||
|
||||
独立的流程场景另见[有界轮询](workflow-poll.md)与[证据恢复](workflow-recovery.md):前者区分请求重试与业务轮次,后者实际注入外部提交后HTTP断线,保留UNKNOWN历史和暂停门控。本机渠道是受控夹具,不能代替持久业务证据。
|
||||
|
||||
## 已可运行:业务事务与任务提交Outbox
|
||||
|
||||
- 独立模块:[task-outbox](../../examples/task-outbox/README.md),[完整前置、命令、Go方法及故障处理](task-outbox.md)。
|
||||
|
||||
85
docs/examples/workflow-poll.md
Normal file
85
docs/examples/workflow-poll.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# 有界轮询:请求重试与业务轮次分别计数
|
||||
|
||||
[完整源码](../../sdk/go/examples/workflow-poll/main.go)通过公共SDK发布POLL定义,启动真实Worker,并请求一个本机HTTP渠道夹具。流程先由TASK向本机渠道提交一次作业,受理回执提供固定operation_id,随后POLL只查询这个作业;平台没有集成任何具体业务渠道。
|
||||
|
||||
同一命令依次运行两个流程:
|
||||
|
||||
| 场景 | 渠道响应 | 平台结果 |
|
||||
| --- | --- | --- |
|
||||
| success | 提交一次;首次查询503,重试PENDING,下一轮DONE | 2个业务轮、3次查询尝试,SUCCEEDED |
|
||||
| exhausted | 提交一次;两次查询都成功返回PENDING | 2个业务轮、2次查询尝试,NEEDS_REVIEW |
|
||||
|
||||
## 1. 环境与启动
|
||||
|
||||
需要Go 1.27.1、MySQL 8.4+,设置专用`ORBIT_MYSQL_DSN`。从仓库根目录初始化:
|
||||
|
||||
```sh
|
||||
go run ./cmd/orbit-migrate up
|
||||
mkdir -p .local
|
||||
chmod 700 .local
|
||||
go run ./cmd/orbit-admin bootstrap \
|
||||
--application poll-example --namespace default --environment development \
|
||||
--queue workflows --workflow-access --workflow-run-access --worker-access \
|
||||
--output .local/poll.credentials.json
|
||||
go run ./cmd/orbit
|
||||
```
|
||||
|
||||
bootstrap只执行一次,凭据文件存在时不会覆盖。另开终端,从仓库根目录运行:
|
||||
|
||||
```sh
|
||||
cd sdk/go
|
||||
ORBIT_URL=http://127.0.0.1:8080 \
|
||||
ORBIT_CREDENTIALS_FILE=../../.local/poll.credentials.json \
|
||||
go run ./examples/workflow-poll
|
||||
```
|
||||
|
||||
程序自行创建并关闭本机HTTP夹具,不需要启动第三方系统,不向公网提交操作。正式接入使用HTTPS/WSS和分角色凭据,将只读查询处理器替换为自己的渠道调用。
|
||||
|
||||
## 2. 定义与调用链
|
||||
|
||||
CreateWorkflowDefinition → PublishWorkflowVersion(v1) → StartWorkflow → 自动Worker提交一次 → 按受理回执中的身份查询 → GetWorkflow/GetWorkflowStep → ListWorkflowPollRounds → GetTaskSource/ListTaskAttempts → Worker排空。
|
||||
|
||||
输入为`{"operation_id":"本次示例的固定操作身份"}`。submit任务只允许一次尝试并使用RECONCILE_ONLY,提交结果未知时必须核查;query通过WorkflowRef("submit")引用已保存受理回执,每轮及同轮重试保持这个身份。结果契约是STRING枚举PENDING/DONE。定义中设置:
|
||||
|
||||
```go
|
||||
poll := orbit.WorkflowPoll{
|
||||
Task: orbit.WorkflowTask{
|
||||
// 处理器、输入/输出契约和队列等完整字段见可运行源码。
|
||||
Retry: orbit.RetryPolicy{
|
||||
MaxAttempts: 2, InitialDelayMS: 1000,
|
||||
MaxDelayMS: 1000, AttemptTimeoutMS: 5000,
|
||||
},
|
||||
TimeoutMS: 20000,
|
||||
RecoverySafety: orbit.RecoveryIdempotent,
|
||||
},
|
||||
MaxRounds: 2, TimeoutMS: 30000, IntervalMS: 1000,
|
||||
Pending: []string{"PENDING"}, Succeeded: []string{"DONE"},
|
||||
}
|
||||
```
|
||||
|
||||
本片段说明预算配置,不是可单独发布的完整定义;源码包含所有必填字段。流程总时限45秒,查询总时限30秒,轮次包含首次;实际任务还受流程及节点原截止限制。
|
||||
|
||||
受控渠道的503表示明确拒绝只读查询,因此返回`WorkerFailure{Code:"QUERY_TEMPORARY", Retryable:true}`。传输错误仍返回普通error,保留未知结果,由冻结的安全策略决定后续。只有明确知道请求失败才可声明WorkerFailure,不能把任意网络超时归类为明确失败。
|
||||
|
||||
HTTP 200的PENDING直接作为成功结果返回给Orbit,不由Worker自己循环或sleep。Weave保存轮间等待、释放Worker槽位,并在原间隔后创建下一轮任务。轮次耗尽不假定外部作业失败,也不重置预算或再次提交外部作业。
|
||||
|
||||
## 3. 预期输出及验证
|
||||
|
||||
前面的scenario、operation、workflow字段每次不同。两个最终结果为:
|
||||
|
||||
```text
|
||||
poll_scenario=success state=SUCCEEDED submissions=1 rounds=2 attempts=3 requests=3 deadline_preserved=true worker_drained=true
|
||||
poll_scenario=exhausted state=NEEDS_REVIEW submissions=1 rounds=2 attempts=2 requests=2 deadline_preserved=true worker_drained=true
|
||||
```
|
||||
|
||||
程序从实际历史读取两轮,每页1条;成功场景第一轮包含FAILED和SUCCEEDED两次尝试,第二轮只有一次SUCCEEDED。耗尽场景两轮请求都成功,逻辑步骤仍进入核查,没有current_task_id或next_poll_at继续运行。每轮任务来源必须指向原实例与轮次,原查询/实例截止保持不变,每个流程只有一次POST提交;Worker实际执行与确认总数包含该提交,所以成功场景共4次、耗尽场景共3次,其中查询请求分别为3次和2次。
|
||||
|
||||
## 4. 失败、重跑与清理
|
||||
|
||||
程序总运行期限60秒,每个Worker排空期限5秒。发生意外状态、权限错误、响应损坏或预算不符会非零退出,不自动换版本、续期或解除核查。
|
||||
|
||||
每次重新运行会创建新的独立示例身份,不代表对上一次未知响应的恢复。生产调用方应持久保存幂等键及完整命令;响应不明时原命令原键核对,不能重新执行此示例来替代恢复。
|
||||
|
||||
正常结束关闭本机渠道与Worker,保留两个流程和所有历史,其中exhausted流程有意保留NEEDS_REVIEW供查看。不要将该示例放进生产定时任务无限创建待核查记录。真实渠道状态和证据需由接入方核实,再通过[人工恢复API](../api/workflow-recovery.md)决定后续;示例不会伪造最终成功。
|
||||
|
||||
回归入口是`make integration`。SDK预算与字段见[定义格式](../api/workflow-definition-format.md),原截止与查询语义见[时钟约定](../13-workflow-clock.md),阶段证据见[P4/P5核对](../reports/phase-4-5.md)。
|
||||
90
docs/examples/workflow-recovery.md
Normal file
90
docs/examples/workflow-recovery.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 提交后回执丢失:核查、暂停与有证据恢复
|
||||
|
||||
[完整源码](../../sdk/go/examples/workflow-recovery/main.go)使用真实Orbit、自动Worker和本机HTTP渠道夹具。夹具先保存外部提交结果,再直接关闭连接,确保Worker实际遇到传输错误。流程进入NEEDS_REVIEW后,程序在受控夹具中模拟一个已经核实事实的管理员,逐项提交恢复决定。
|
||||
|
||||
这是一份可运行的SDK接入与故障示例。夹具数据仅在本次进程内保存;真实系统需要持久证据和真实授权,不能把下面的演示核查逻辑接成任意业务的自动放行。
|
||||
|
||||
## 1. 启动
|
||||
|
||||
需要Go 1.27.1、MySQL 8.4+,设置专用`ORBIT_MYSQL_DSN`。仓库根目录执行:
|
||||
|
||||
```sh
|
||||
go run ./cmd/orbit-migrate up
|
||||
mkdir -p .local
|
||||
chmod 700 .local
|
||||
go run ./cmd/orbit-admin bootstrap \
|
||||
--application workflow-recovery-example --namespace default --environment development \
|
||||
--queue workflows --workflow-access --workflow-run-access --worker-access \
|
||||
--workflow-control-access --workflow-recovery-access --workflow-audit-access \
|
||||
--output .local/workflow-recovery.credentials.json
|
||||
go run ./cmd/orbit
|
||||
```
|
||||
|
||||
初始化一次;原凭据文件不会被覆盖。另开终端,从仓库根目录运行:
|
||||
|
||||
```sh
|
||||
cd sdk/go
|
||||
ORBIT_URL=http://127.0.0.1:8080 \
|
||||
ORBIT_CREDENTIALS_FILE=../../.local/workflow-recovery.credentials.json \
|
||||
go run ./examples/workflow-recovery
|
||||
```
|
||||
|
||||
开发凭据包含定义、启动、Worker、控制、恢复及审计权限,便于单命令演示。正式接入应使用HTTPS/WSS,并为业务Worker与管理员分别签发所需权限;Worker执行权不自动授予恢复权。
|
||||
|
||||
## 2. 故障与完整调用顺序
|
||||
|
||||
流程包含publish和record两个顺序任务。输入为原操作身份及精确序号9007199254740993;publish只允许一次请求尝试,安全策略为RECONCILE_ONLY。record只在前一步被核实成功并解除控制门控后处理已确认回执。
|
||||
|
||||
1. 创建定义、发布v1并启动;Worker向本机渠道POST一次。渠道保存结果,然后关闭连接,不返回HTTP响应。
|
||||
2. 处理器返回普通error。Orbit保留UNKNOWN尝试,任务和流程进入NEEDS_REVIEW,不重发POST。
|
||||
3. 查询当前步骤、任务及尝试,确认唯一尝试已结束、Worker处理器已退出且结果报告已确认,再显式PAUSE流程。
|
||||
4. 用原操作身份GET渠道证据,核对operation_id、状态CONFIRMED及精确序号。夹具能证明这是唯一一次提交,且提交处理已经结束;真实系统必须另行证明历史执行已结清,仅有一次GET成功不够。
|
||||
5. `ReviewWorkflowTask`提交执行结论:当前实例版本、任务版本、execution_settled=true、结果及证据引用。使用原键原命令再调用一次,确认同一审计回执。
|
||||
6. 等待核查结果被内部消费,重新读取步骤及实例版本;`ResolveWorkflowStep`确认逻辑步骤成功,保留原激活。
|
||||
7. `ContinueWorkflow`声明核查事项已解决。此时仍为paused,record步骤保持BLOCKED,没有任务被提前建立。
|
||||
8. 查询三条管理审计,核对操作人、原因和同一证据引用;再单独调用`ControlWorkflow(RESUME)`释放暂停。
|
||||
9. Worker完成record,流程成功;查询原尝试仍是UNKNOWN,原实例截止未变,最后排空Worker。
|
||||
|
||||
每个管理决定都有独立稳定操作键。核查使用实例revision,控制使用control_revision,两者不可互换。读到新事实后重新核查再形成下一条命令;不能自动把STATE_CONFLICT改为最新版本重报。
|
||||
|
||||
## 3. SDK关键字段
|
||||
|
||||
完整程序使用[六类恢复API中的三个方法](../api/workflow-recovery.md)。执行核查示意:
|
||||
|
||||
```go
|
||||
command := orbit.ReviewWorkflowTaskRequest{
|
||||
WorkflowRecoveryDecision: orbit.WorkflowRecoveryDecision{
|
||||
ExpectedRevision: currentStep.Revision,
|
||||
Reason: "已核实原操作、结果和所有相关执行已结束",
|
||||
EvidenceRef: evidenceRef,
|
||||
},
|
||||
TaskID: task.ID, ExpectedTaskRevision: task.Revision,
|
||||
ExecutionSettled: true,
|
||||
Outcome: orbit.TaskSucceeded, Result: verifiedResult,
|
||||
}
|
||||
receipt, err := client.ReviewWorkflowTask(ctx, workflowID, reviewKey, command)
|
||||
if err != nil { return err }
|
||||
// receipt是管理事务的冻结回执,下一项决定前应读取新的当前事实。
|
||||
```
|
||||
|
||||
示例证据引用形如`evidence:local-fixture/<operation>`,明确表示本地受控夹具,不能冒充持久业务证据。平台只保存受控引用及审计,不请求该地址。真实证据应由接入方按权限、保留及审计要求持久保存。
|
||||
|
||||
## 4. 输出与历史保护
|
||||
|
||||
程序先打印可查询的操作身份和workflow ID,最终应输出:
|
||||
|
||||
```text
|
||||
recovery_verified=true state=SUCCEEDED submissions=1 unknown_history_preserved=true review_deduplicated=true pause_preserved=true audits=3 sequence=9007199254740993 worker_drained=true
|
||||
```
|
||||
|
||||
这些标志来自实际API和Worker统计:只发生1次外部POST;同键核查返回同一audit_id;task-review、step-resolve、continue三条审计具有原因、主体和证据;恢复没有解除PAUSE;旧UNKNOWN尝试和结束时间没有改写;两项业务任务均已结束并确认。
|
||||
|
||||
最终任务的权威结果可以是SUCCEEDED,同时原尝试保留UNKNOWN,两者并不矛盾。核查增加新的决定和证据,不能抹去当时无法确认结果的事实。
|
||||
|
||||
## 5. 失败及重跑边界
|
||||
|
||||
本次运行最多45秒,Worker排空最多5秒。任何身份、证据、结果或版本不符都会非零退出;失败不会自动调用RetryWorkflowStep、换键重发外部POST或解除核查。
|
||||
|
||||
重新运行命令创建新的独立演示流程,不是对上一流程的恢复。生产接入方必须持久保存每条命令和幂等键;响应未知时使用原命令原键核对,旧版本冲突需要重新核查。示例退出时关闭本机HTTP夹具与Worker,平台流程和历史保留;若中途失败,夹具停止后不能再用其内存结果作为后续人工恢复依据。
|
||||
|
||||
`make integration`运行此示例以及独立SDK中的六类恢复回归;另有节点强杀、恢复代次和原预算验证。记录见[P4/P5核对](../reports/phase-4-5.md)。这不替代真实业务的幂等、权限、证据和生产故障恢复验收。
|
||||
Reference in New Issue
Block a user