feat: add durable safe retries with uncertainty tracking and SDK examples
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
| `/v1/queues` 创建与查询 | P1 已实现 | [队列接口](queues.md) |
|
||||
| 凭据校验与初始化 CLI | P1 已实现 | [鉴权](authentication.md) |
|
||||
| `/v1/worker/connect` | P2 已实现并通过阶段验收 | [Worker 契约](worker.md)、[Schema](worker-schema.md) |
|
||||
| 任务冻结策略与 Worker `retryable` | P3 安全重试已实现 | [安全重试](retries.md) |
|
||||
| `/v1/schedules` 与计划管理 | 计划 P3,尚未实现 | [协议草案](../03-http-websocket-protocol.md) |
|
||||
| `/v1/events/connect` | 计划 P3,尚未实现 | [协议草案](../03-http-websocket-protocol.md) |
|
||||
| `/v1/workflows` 与流程管理 | 计划 P4–P5,尚未实现 | [流程接口规划](workflows.md) |
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
任务代表一次受理的工作,尝试代表其中一次执行代次。领取后未获开始授权就过期的尝试也会保留,并消耗尝试预算。重连恢复同一次尝试,不创建新的尝试记录。
|
||||
|
||||
证据保存 Worker 的迟到、未开始或冲突报告。`outcome=SUCCEEDED` 只是原报告的声明;`disposition=EVIDENCE_ONLY` 表示采集时仅作为证据保存,不能解释为任务成功。权威任务状态与结果使用 [GetTask](tasks.md) 查询。当前接口不执行人工裁定、恢复或重试。
|
||||
证据保存 Worker 的迟到、未开始或冲突报告。`outcome=SUCCEEDED` 只是原报告的声明;`disposition=EVIDENCE_ONLY` 表示采集时仅作为证据保存,不能解释为任务成功。权威任务状态与结果使用 [GetTask](tasks.md) 查询。当前只读接口不执行人工裁定、恢复或重试。平台自动重试保留每次失败结果;其规则及任务 effect_uncertain 见 [安全重试](retries.md)。
|
||||
|
||||
四个接口均要求 `Authorization: Bearer <token>`、`tasks:read` 动作及任务所属队列范围。凭据确定命名空间,每次调用都重新校验有效性。ID 是 32 位小写十六进制字符串,全部父子关系都校验;知道尝试 ID 或证据 ID 不代表有权读取。
|
||||
|
||||
@@ -60,6 +60,7 @@ curl "$ORBIT_URL/v1/tasks/$TASK_ID/attempts/$ATTEMPT_ID/evidence/$EVIDENCE_ID" \
|
||||
| authorized_at | UTC RFC 3339 / 否 | 获批开始时间;省略表示尚未获批,不证明外部效果是否发生 |
|
||||
| ended_at | UTC RFC 3339 / 否 | 尝试结束时间;尚未结束时省略 |
|
||||
| error_code | string / 否 | 稳定错误分类,无错误时省略 |
|
||||
| retryable | bool / 否 | Worker 对明确失败的可安全重试声明,false 时省略;不证明平台实际安排了重试 |
|
||||
| created_at、updated_at | UTC RFC 3339 / 是 | 创建和最近更新时间,最多微秒精度 |
|
||||
| session_id、worker_instance_id | string / 仅详情 | 当前或最后归属会话及进程身份;会话可能因恢复改变,不是完整归属变更日志 |
|
||||
| result | JSON / 仅详情,可省略 | 此尝试已保存的结果;无结果时省略,与追加证据分开 |
|
||||
@@ -108,6 +109,7 @@ curl "$ORBIT_URL/v1/tasks/$TASK_ID/attempts/$ATTEMPT_ID/evidence/$EVIDENCE_ID" \
|
||||
| id、task_id、attempt_id | string / 是 | 证据及已校验的父资源标识 |
|
||||
| outcome | string / 是 | 原报告的 SUCCEEDED、FAILED 或 UNKNOWN |
|
||||
| error_code | string / 否 | 原报告的错误分类,空时省略 |
|
||||
| retryable | bool / 否 | 原失败报告的重试声明,false 时省略;证据不触发执行 |
|
||||
| reason | string / 是 | LEASE_LOST(授权失效)、NOT_STARTED(未获批开始)、RESULT_CONFLICT(与已保存结果冲突) |
|
||||
| disposition | string / 是 | 固定 EVIDENCE_ONLY,描述采集处理方式 |
|
||||
| created_at | UTC RFC 3339 / 是 | 首次保存时间,相同身份的重复报告不刷新 |
|
||||
|
||||
117
docs/api/retries.md
Normal file
117
docs/api/retries.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 安全重试 v1
|
||||
|
||||
状态:P3 的安全重试已实现。使用现有任务提交与 Worker 结果接口,无独立的自动重试路由;Cron、运行中取消、人工恢复与可靠结果订阅继续按 [计划](../08-development-plan.md) 开发。验证证据见 [本阶段报告](../reports/phase-3-safe-retry.md)。
|
||||
|
||||
## 1. 三种不同的重报
|
||||
|
||||
| 行为 | 身份与效果 |
|
||||
| --- | --- |
|
||||
| HTTP 受理结果不明,重报提交 | 保留原 Idempotency-Key 和内容,返回原任务,不增加尝试 |
|
||||
| WS 控制结果不明,重报 start/finish | 保留尝试和 message_id,返回原回执,不再次调用处理器 |
|
||||
| 执行失败或租约失效,平台安排重试 | 保留 Task.ID;到期后领取新 AttemptID/Epoch,重新消耗一次预算 |
|
||||
|
||||
HTTP 调用方自行用原身份核对受理结果,自动 Worker 管理第二类控制确认,平台根据持久化事实决定第三类。业务效果是否可重复执行,需要处理器自身保证,不能只靠连接重试。
|
||||
|
||||
## 2. 冻结策略与结果分类
|
||||
|
||||
提交时设置 `retry.max_attempts / initial_delay_ms / max_delay_ms / attempt_timeout_ms`、`recovery_safety`、任务总时限及可选最晚开始时间。默认 max_attempts=1、recovery_safety=RECONCILE_ONLY,不自动增加业务尝试。字段范围见 [任务接口](tasks.md)。
|
||||
|
||||
Worker 的 `task.finish` 新增可选布尔字段 `retryable`,省略为 false;只有 outcome=FAILED 才能为 true。SUCCEEDED/UNKNOWN 携带 true 返回 INVALID_ARGUMENT。该字段表示明确失败可安全再试,**不能用于把未知效果伪装成明确失败**。
|
||||
|
||||
以下均要求报告仍有当前有效授权;无授权的报告只保存证据:
|
||||
|
||||
| 当前事实 | 有剩余预算、截止允许时 | 不能重试时 |
|
||||
| --- | --- | --- |
|
||||
| SUCCEEDED | 完成任务,清除 effect_uncertain | 同左 |
|
||||
| FAILED,retryable=false | 不安排重试 | 无历史不确定效果则 FAILED,否则 NEEDS_REVIEW |
|
||||
| FAILED,retryable=true | 退避后 WAITING | 无历史不确定效果则 FAILED,否则 NEEDS_REVIEW |
|
||||
| UNKNOWN 或已获开始授权后租约到期;RECONCILE_ONLY | NEEDS_REVIEW | NEEDS_REVIEW |
|
||||
| UNKNOWN 或已获开始授权后租约到期;IDEMPOTENT | effect_uncertain=true,退避后 WAITING | NEEDS_REVIEW,保留不确定性 |
|
||||
| 领取后未获开始授权即到期 | 退避后 WAITING,仍消耗已领取次数 | 无历史不确定效果则 FAILED,否则 NEEDS_REVIEW |
|
||||
|
||||
存在历史不确定效果时,只有 IDEMPOTENT 允许继续自动尝试。此声明要求跨进程、跨尝试、并发及迟到执行都具有业务幂等保障,例如持久化操作键、唯一约束、返回原结果及资源侧执行代次校验。Task.ID 在自动重试中稳定;AttemptID 会变化,不能作为跨尝试的业务幂等键。平台不会因为新尝试开始而保证旧进程已经停止。
|
||||
|
||||
`effect_uncertain` 是任务详情、摘要和派发任务中的可选布尔字段,false 时省略。只要曾发生尚未核实的授权执行,后来的明确失败、未开始即到期、总时限耗尽或取消都不会把该事实抹掉。当前有效尝试成功可自动清除;迟到成功证据不能清除。人工核查解除的管理接口尚未实现。
|
||||
|
||||
## 3. 退避、预算与持久化
|
||||
|
||||
消费第 n 次领取预算后,退避上限为:
|
||||
|
||||
```text
|
||||
base = min(initial_delay_ms × 2^(n-1), max_delay_ms)
|
||||
delay_ms ∈ [ceil(base/2), base]
|
||||
available_at = 本次结束事务中的数据库时间 + delay_ms
|
||||
```
|
||||
|
||||
抖动使用 AttemptID 生成确定值,同一尝试重复计算不改变时间;指数倍增先检查上限,避免溢出。返回 WAITING 表示下一次最早可领取时间,实际派发还受容量、队列暂停、流程门控、凭据、扫描与服务可用性影响,不承诺准点开始。
|
||||
|
||||
- 领取就消耗次数,包含未获开始授权的尝试。重连、ACK 重报和取消都不重置次数。
|
||||
- 下一次 available_at 必须严格早于总截止和最晚开始时间;等于截止也不能安排重试。领取/开始仍重新核验数据库时间。
|
||||
- attempt_timeout_ms 从获批开始计算;每次尝试重新计时,任务总截止保持不变。
|
||||
- 尝试结果、retryable、任务状态、不确定标记、下一时间、许可释放、事件及结果操作审计/回执在同一事务提交。后台租约回收保存包含节点身份与决策的持久化事件。
|
||||
- 等待重试期间不占执行许可,任务不带 result/completed_at;上一尝试的错误和结果保留在 [执行历史](execution-history.md)。NEEDS_REVIEW 也没有 completed_at。
|
||||
- 重启后以数据库 available_at 继续领取,不依赖某一节点内存中的定时器。
|
||||
|
||||
`task.requeued` 等持久化事件记录 decision、delay_ms、next_available_at、attempt_count、max_attempts、effect_uncertain。decision 包括 RETRYABLE_FAILURE、IDEMPOTENT_UNKNOWN、UNSTARTED_LEASE_EXPIRED、ATTEMPT_LIMIT、DEADLINE_EXCEEDED、RECONCILIATION_REQUIRED 等。当前可靠事件订阅尚未开放。
|
||||
|
||||
当前取消接口仅支持不归属流程的 WAITING 任务。若等待的是历史效果不明后的重试,取消会记录意图、停止后续尝试并进入 NEEDS_REVIEW,不能宣称此前业务已撤销;其他 WAITING 任务可进入 CANCELLED。运行中取消仍未开放。
|
||||
|
||||
## 4. SDK 接入
|
||||
|
||||
生产者在提交请求中设置有限策略,以下为函数片段:
|
||||
|
||||
```go
|
||||
accepted, err := client.SubmitTask(ctx, operationKey, orbit.SubmitTaskRequest{
|
||||
QueueID: queueID,
|
||||
Handler: "text.normalize", HandlerVersion: "v1", PayloadVersion: "v1",
|
||||
Payload: json.RawMessage(`{"text":" safe retry "}`),
|
||||
RecoverySafety: orbit.RecoveryReconcileOnly,
|
||||
TaskTimeoutMS: 60000,
|
||||
Retry: orbit.RetryPolicy{
|
||||
MaxAttempts: 3, InitialDelayMS: 500, MaxDelayMS: 2000,
|
||||
AttemptTimeoutMS: 10000,
|
||||
},
|
||||
})
|
||||
if err != nil { return err }
|
||||
fmt.Println(accepted.Task.ID)
|
||||
```
|
||||
|
||||
处理器在已确认可安全重试的失败分支返回:
|
||||
|
||||
```go
|
||||
return nil, &orbit.WorkerFailure{
|
||||
Code: "TEMPORARY_REJECTION",
|
||||
Retryable: true,
|
||||
Details: json.RawMessage(`{"reason_ref":"diagnostics/temporary"}`),
|
||||
}
|
||||
```
|
||||
|
||||
WorkerFailure 默认 Retryable=false;普通 error、panic、runtime.Goexit 和非法结果仍上报 UNKNOWN,由冻结的 recovery_safety 决定是否允许新尝试。不要把任意网络超时转成 Retryable=true。底层 SDK 对应 `WorkerFinish.Retryable`;其消息结构见 [Worker 契约](worker.md)。
|
||||
|
||||
## 5. 运行完整示例
|
||||
|
||||
源码:[examples/retry/main.go](../../sdk/go/examples/retry/main.go)。先运行当前迁移、服务,并按 [Worker 初始化步骤](../sdk/worker.md#1-可运行的文本规范化示例) 准备具有提交、读取、消费权限的专用凭据。从仓库根目录执行:
|
||||
|
||||
```sh
|
||||
cd sdk/go
|
||||
ORBIT_URL=http://127.0.0.1:8080 \
|
||||
ORBIT_CREDENTIALS_FILE=../../.local/worker.credentials.json \
|
||||
go run ./examples/retry
|
||||
```
|
||||
|
||||
输入为 `{"text":" safe retry "}`。第一次处理在产生效果前模拟明确拒绝,第二次执行真正的文本规范化;重试等待由平台持久化安排。
|
||||
|
||||
```text
|
||||
task=<任务ID> state=SUCCEEDED attempts=2 first_retryable=true result={"text":"SAFE RETRY"}
|
||||
worker_drained=true started=2 confirmed=2 running=0
|
||||
```
|
||||
|
||||
confirmed 统计已确认的尝试报告,包含第一次失败,不是成功任务数。示例通过尝试查询核对第一次 FAILED/retryable=true 与第二次 SUCCEEDED。每次运行生成独立操作和处理器身份,不连接外部业务系统;正常结束排空,历史保留。内存计数只用于注入一次故障,不能用作生产幂等存储。错误时非零退出并尝试有界收尾。
|
||||
|
||||
## 6. 回执与升级兼容
|
||||
|
||||
同 message_id 改变 retryable 或其他内容返回 IDEMPOTENCY_CONFLICT。保持同一结果而换 message_id 时,返回第一次已应用的结果回执;即使任务已在后续尝试成功,旧失败回执仍可能写着 WAITING。当前状态用 GetTask 查询;CommandResult.lease 仍只描述旧尝试当前是否有权执行。
|
||||
|
||||
迁移 000005 增加 effect_uncertain、尝试/证据 retryable 以及结果回执索引。已有 NEEDS_REVIEW 保守回填 effect_uncertain=true,保留原状态、版本和策略。已有结果摘要在 retryable 省略或 false 时保持兼容;true 属于不同语义。
|
||||
|
||||
本开发版本首次启用已冻结的多次尝试策略:旧的未结束任务若声明 max_attempts>1 / IDEMPOTENT,升级后也会按本页规则执行;不重写策略、不重置预算、不自动恢复已有 NEEDS_REVIEW。旧服务不识别新 Schema 版本,应停止旧节点后迁移并启动统一版本;尚未验证混合版本滚动升级或向旧二进制回退。SDK 尚未发布版本,自研 Worker 使用 retryable 前应升级服务端。
|
||||
@@ -1,8 +1,8 @@
|
||||
# 任务接口 v1
|
||||
|
||||
状态:P1 已实现并通过真实 MySQL 与 SDK 回归。当前提供受理、查询、分页和待执行取消;P2 已提供 Worker 执行与历史查询,策略重试在 P3 实现。
|
||||
状态:P1 已实现并通过真实 MySQL 与 SDK 回归。当前提供受理、查询、分页和待执行取消;P2 已提供 Worker 执行与历史查询,P3 已接入 [安全重试](retries.md)。
|
||||
|
||||
P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生 LEASED、RUNNING、SUCCEEDED、FAILED、NEEDS_REVIEW 等状态,见 [Worker 契约](worker.md);[尝试与证据查询](execution-history.md) 可核对每次执行,自动策略重试尚未完成。
|
||||
P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生 LEASED、RUNNING、SUCCEEDED、FAILED、NEEDS_REVIEW 等状态,见 [Worker 契约](worker.md);[尝试与证据查询](execution-history.md) 可核对每次执行;重试以冻结策略、明确失败分类及未知效果安全声明为依据。
|
||||
|
||||
## 公共约定
|
||||
|
||||
@@ -28,7 +28,7 @@ P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生
|
||||
| start_deadline_at | time / null | 无独立限制 | 必须晚于当前时间,不早于 available_at,不晚于总截止时间 |
|
||||
| task_timeout_ms | uint64 | 0 或省略为 86400000 | 从受理时计算的总时限,最大 30 天(2592000000 毫秒) |
|
||||
| recovery_safety | string | RECONCILE_ONLY | 也可为 IDEMPOTENT;必须基于处理器真实的幂等能力声明 |
|
||||
| retry | object | 见下表 | 受理时冻结策略;P2 已执行尝试预算及时限,策略退避重试在 P3 实现 |
|
||||
| retry | object | 见下表 | 受理时冻结策略;包含首次领取的有限尝试、退避及每次时限,见 [安全重试](retries.md) |
|
||||
|
||||
处理器及版本名称以 ASCII 字母或数字开头,后续只能包含字母、数字、`_ . : -`。时间年份为 2000–9998。
|
||||
|
||||
@@ -90,7 +90,7 @@ curl -i "$ORBIT_URL/v1/tasks" \
|
||||
}
|
||||
```
|
||||
|
||||
可选响应字段:start_deadline_at、result、cancel_requested_at、completed_at、business_key,未设置时省略。终态有 completed_at。状态集合为 WAITING / LEASED / RUNNING / NEEDS_REVIEW / SUCCEEDED / FAILED / CANCELLED;P1 仅主动产生 WAITING 和 CANCELLED。
|
||||
可选响应字段:start_deadline_at、result、cancel_requested_at、completed_at、business_key,未设置时省略。effect_uncertain=true 表示存在尚未核实的授权执行效果,false 时省略;详情、摘要和 Worker 派发均可返回。等待重试时 result/completed_at 省略,历史结果在尝试中保留。终态有 completed_at。状态集合为 WAITING / LEASED / RUNNING / NEEDS_REVIEW / SUCCEEDED / FAILED / CANCELLED;P1 仅主动产生 WAITING 和 CANCELLED。
|
||||
|
||||
### 幂等与失败处理
|
||||
|
||||
@@ -164,7 +164,7 @@ curl "$ORBIT_URL/v1/tasks/$TASK_ID:cancel" \
|
||||
|
||||
expected_revision 必填,为非零 uint64 的十进制字符串;reason 必填且不能全为空白,最多 1024 个 Unicode 字符。服务端以版本和状态条件更新,只有一个竞争请求能成功。
|
||||
|
||||
200 的 data 为 `{"task":<完整最新任务>,"deduplicated":false}`。示例任务会变为 state=CANCELLED、revision="2",写入 cancel_requested_at/completed_at,并同事务追加取消事件及审计。首次取消之后使用相同键、原因和**原 expected_revision**重报,返回原取消回执且 deduplicated=true。换键重报已取消任务返回 409。
|
||||
200 的 data 为 `{"task":<完整最新任务>,"deduplicated":false}`。示例任务会变为 state=CANCELLED、revision="2",写入 cancel_requested_at/completed_at,并同事务追加取消事件及审计。首次取消之后使用相同键、原因和**原 expected_revision**重报,返回原取消回执且 deduplicated=true。换键重报已取消任务返回 409。若 WAITING 来自未知效果后的自动重试,取消会保存 cancel_requested_at 并转 NEEDS_REVIEW,保留 effect_uncertain=true、completed_at 为空,停止后续尝试但不宣称此前效果已经撤销。
|
||||
|
||||
SDK:`Client.CancelTask(ctx, taskID, key, request)`。此操作不承诺回滚任何已发生的外部业务效果。
|
||||
|
||||
@@ -186,4 +186,4 @@ SDK:`Client.CancelTask(ctx, taskID, key, request)`。此操作不承诺回滚
|
||||
{"request_id":"44444444444444444444444444444444","error":{"code":"IDEMPOTENCY_CONFLICT","message":"idempotency key was used with different content"}}
|
||||
```
|
||||
|
||||
批量受理、人工重试和运行中取消尚未实现。[执行历史](execution-history.md) 已提供尝试与证据的列表和详情。完整可运行程序见 [SDK 任务示例](../../sdk/go/examples/tasks/main.go),已验证异常场景见 [P1 回归报告](../reports/phase-1.md)。SDK 忽略未知响应字段;更改已有字段语义需升级契约和回归。
|
||||
有限自动重试已实现,具体分类、退避、回执和升级边界见 [安全重试](retries.md)。批量受理、人工重试和运行中取消尚未实现。[执行历史](execution-history.md) 已提供尝试与证据的列表和详情。完整可运行程序见 [SDK 任务示例](../../sdk/go/examples/tasks/main.go),已验证异常场景见 [P1 回归报告](../reports/phase-1.md)。SDK 忽略未知响应字段;更改已有字段语义需升级契约和回归。
|
||||
|
||||
@@ -30,9 +30,10 @@ ACK 的 body 根据原请求选择:worker.capacity → welcome,worker.resume
|
||||
- epoch、sequence、revision 为规范十进制字符串:范围 1–18446744073709551615,无前导零和正号。SDK 使用 uint64,线上使用字符串。
|
||||
- instance_secret/lease_secret 必须是 32 字节秘密的严格无填充 base64url,长度 43;知道资源 ID 无法替代秘密。
|
||||
- 结果为非 null JSON;FAILED/UNKNOWN 必须携带稳定 error_code;SUCCEEDED 省略或使用空 error_code。
|
||||
- finish.retryable 为可选布尔值,默认 false;true 仅允许 FAILED,Schema 与服务端都校验。派发任务可带 effect_uncertain=true,表示此前授权执行的效果尚未核实。业务重试条件见 [安全重试](retries.md)。
|
||||
- 返回时间使用 UTC RFC 3339,最多微秒精度。验证器应开启 format 断言以校验实际日历日期。
|
||||
|
||||
本轮收紧了开发期此前允许的省略控制字段、非规范计数字符串和数组内字段别名。当前 SDK 一直发送完整规范字段,不需迁移;其他自研客户端应按本文件更新。SDK 尚未发布首个版本。将来扩展消息类型或改变字段语义必须同时更新 Schema、服务端、SDK 与兼容回归。
|
||||
P2 收紧了开发期此前允许的省略控制字段、非规范计数字符串和数组内字段别名。当前 SDK 一直发送完整规范字段,不需迁移;其他自研客户端应按本文件更新。SDK 尚未发布首个版本。将来扩展消息类型或改变字段语义必须同时更新 Schema、服务端、SDK 与兼容回归。
|
||||
|
||||
## Schema 与运行时的边界
|
||||
|
||||
|
||||
@@ -88,9 +88,9 @@ capacity 使用单调递增序号和覆盖值。同序号同内容可重复,
|
||||
| --- | --- | --- |
|
||||
| task.start | AttemptRef | 领取后再次检查当前会话、尝试、租约、队列暂停及流程门控,保存 RUNNING 后确认 |
|
||||
| task.renew | AttemptRef | 只续期当前 RUNNING;新消息可续租,重复原消息只取回原回执 |
|
||||
| task.finish | ref: AttemptRef、outcome、result、error_code | 保存明确结果或未知效果;返回操作回执和当前授权 |
|
||||
| task.finish | ref: AttemptRef、outcome、result、error_code、可选 retryable | 保存明确结果或未知效果;返回操作回执和当前授权 |
|
||||
|
||||
outcome 为 SUCCEEDED / FAILED / UNKNOWN。result 为非 null JSON 或证据引用。FAILED/UNKNOWN 必须带 1–64 字符的稳定 error_code;SUCCEEDED 不带错误分类。当前 FAILED 成为任务失败终态,UNKNOWN 转 NEEDS_REVIEW;策略化自动重试在 P3 实现。
|
||||
outcome 为 SUCCEEDED / FAILED / UNKNOWN。result 为非 null JSON 或证据引用。FAILED/UNKNOWN 必须带 1–64 字符的稳定 error_code;SUCCEEDED 不带错误分类。retryable 省略为 false,只有 FAILED 可设 true,表示此次明确失败可安全重试。FAILED/true 受次数与时限限制进入退避;UNKNOWN 仅在冻结策略为 IDEMPOTENT 且预算允许时自动恢复,其他情况转 NEEDS_REVIEW。历史不确定效果不会被后续失败覆盖;完整规则见 [安全重试](retries.md)。
|
||||
|
||||
开始、续租和结束返回 CommandResult:
|
||||
|
||||
@@ -120,7 +120,7 @@ receipt 是原操作的不可变确认;lease 是本次重新核验的执行权
|
||||
- 原 start 回执可能仍表示 RUNNING,但当前 lease.active=false;旧 ACK 不能让已过期、已结束或已被替换的尝试复活。
|
||||
- 有效期不超过租约窗口、会话期限、任务总截止和从 authorized_at 起计算的单次尝试时限。
|
||||
- 客户端需要扣除请求总耗时及安全余量,用本地单调时钟停止执行,不能假定本地墙上时钟与数据库一致。
|
||||
- 同结果重报不重复释放许可或产生成功事件。同消息不同内容返回 IDEMPOTENCY_CONFLICT。
|
||||
- 同结果重报不重复释放许可或产生成功事件;即使换 message_id,也返回该结果第一次已应用的回执,不借用后续尝试的状态。同消息改变内容或 retryable 返回 IDEMPOTENCY_CONFLICT。
|
||||
- 迟到、未开始或与已保存结果冲突的内容,经过身份和秘密验证后保存为 EVIDENCE_ONLY;不改变任务权威结果,不当作新的执行授权。
|
||||
- 断线后原结果仍保留稳定消息 ID,可在新会话恢复/核对后重报。不会因重连创建第二次业务执行。
|
||||
|
||||
@@ -132,12 +132,12 @@ receipt 是原操作的不可变确认;lease 是本次重新核验的执行权
|
||||
|
||||
每个节点每 2 秒发起一批恢复扫描,每批最多 100 项:
|
||||
|
||||
- 未批准开始即到期:有剩余次数和截止时间时,延后 1 秒重派;否则 FAILED。
|
||||
- 已批准开始后失联:NEEDS_REVIEW,尝试 UNKNOWN,不能因网络超时断定业务未生效。
|
||||
- WAITING 超过总截止或最晚开始时间:FAILED。
|
||||
- 未批准开始即到期:有剩余次数和截止时间时按冻结策略退避;否则无历史不确定效果为 FAILED,有未核实效果为 NEEDS_REVIEW。
|
||||
- 已批准开始后失联:尝试 UNKNOWN;RECONCILE_ONLY 转 NEEDS_REVIEW,IDEMPOTENT 仅在次数及时限允许时退避重试,并保留 effect_uncertain。
|
||||
- WAITING 超过总截止或最晚开始时间:无历史不确定效果为 FAILED,否则 NEEDS_REVIEW。
|
||||
- 回收再次锁定队列、任务、尝试并读取数据库时间,与续租/完成串行化;多个节点不能重复释放许可或重复终结事件。
|
||||
|
||||
此处的安全回收不是完整重试引擎;IDEMPOTENT 的自动重试和策略退避仍是 P3 工作。
|
||||
安全重试已使用同一决策逻辑处理有效结果与租约回收;下一执行时间持久化在任务中,重启不丢失等待。当前不提供人工重试或核查解除接口。
|
||||
|
||||
## 6. 连接保护与当前边界
|
||||
|
||||
|
||||
Reference in New Issue
Block a user