feat: add durable relative task delays and outage recovery regression
This commit is contained in:
@@ -10,6 +10,12 @@
|
||||
- 覆盖:8个并发调用/20项领取、业务回滚、旧代次拒绝、HTTP回执丢失、三处MySQL COMMIT回执丢失及发送进程SIGKILL恢复。
|
||||
- 边界:业务侧两张表均无外键,22个中文注释字段;固定发送预算,REVIEW保留原记录供业务管理核查,核心SDK不引入数据库依赖。
|
||||
|
||||
## 已可运行:相对延迟执行
|
||||
|
||||
- [完整命令、输入输出和失败处理](delayed-task.md),[SDK源码](../../sdk/go/examples/delayed-task/main.go)。
|
||||
- delay_ms按数据库首次受理时间计时,实际Worker执行后查询开始时间;重报原请求仍返回原任务及时间。
|
||||
- 自动失败重试单独使用retry策略,初始延迟不在每次尝试中重新计时。
|
||||
|
||||
## 已可运行:健康检查
|
||||
|
||||
- 源码:[SDK 健康检查](../../sdk/go/examples/health/main.go)。
|
||||
|
||||
59
docs/examples/delayed-task.md
Normal file
59
docs/examples/delayed-task.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 相对延迟与原请求恢复
|
||||
|
||||
源码:[delayed-task/main.go](../../sdk/go/examples/delayed-task/main.go)。示例提交等待1500毫秒的对象任务,启动自动Worker,核对实际执行历史,再在任务完成后重报原请求。处理器原样返回示例JSON,无外部副作用。
|
||||
|
||||
## 准备与运行
|
||||
|
||||
先完成[迁移和Worker凭据初始化](../sdk/worker.md#1-可运行的文本规范化示例),启动当前Orbit。凭据需要任务提交、读取及Worker连接/消费权限,队列须有可用额度。仓库根目录执行:
|
||||
|
||||
```sh
|
||||
cd sdk/go
|
||||
ORBIT_URL=http://127.0.0.1:8080 \
|
||||
ORBIT_CREDENTIALS_FILE=../../.local/worker.credentials.json \
|
||||
go run ./examples/delayed-task
|
||||
```
|
||||
|
||||
也可用ORBIT_TOKEN和ORBIT_QUEUE_ID覆盖凭据文件中的对应值。示例总上下文20秒、任务总时限15秒、单次执行5秒、最多1次尝试;每次运行使用独立操作及处理器身份,不会消费其他处理器的任务。
|
||||
|
||||
预期输出:
|
||||
|
||||
```text
|
||||
task=<任务ID> delay_ms=1500
|
||||
state=SUCCEEDED attempts=1 no_early_start=true original_receipt=true worker_drained=true
|
||||
```
|
||||
|
||||
检查依据是同一个数据库的CreatedAt、AvailableAt及尝试创建/开始时间,不把客户端墙钟或sleep结束当作执行授权。Worker正常排空后退出;任务、结果和审计保留。
|
||||
|
||||
## SDK调用
|
||||
|
||||
以下为函数中的调用片段,完整imports及错误处理见源码:
|
||||
|
||||
```go
|
||||
request := orbit.SubmitTaskRequest{
|
||||
QueueID: queueID,
|
||||
Handler: "object.prepare", HandlerVersion: "v1", PayloadVersion: "v1",
|
||||
Payload: json.RawMessage(`{"object_ref":"objects/input"}`),
|
||||
DelayMS: 5000,
|
||||
TaskTimeoutMS: 60000,
|
||||
Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000},
|
||||
}
|
||||
accepted, err := client.SubmitTask(ctx, operationKey, request)
|
||||
if err != nil { return err }
|
||||
fmt.Println(accepted.Task.ID, accepted.Task.AvailableAt)
|
||||
```
|
||||
|
||||
`delay_ms`相对首次成功受理事务中的数据库时间计算,保存为`available_at`。同一幂等键重报仍提交原等待时长;不按当前时间重算available_at,不生成新键。响应是原受理回执,即使任务后来已经成功,回执中仍可能是WAITING;当前状态通过GetTask查询。
|
||||
|
||||
指定绝对时刻时使用AvailableAt并令DelayMS为0。非零delay_ms和available_at不能同时提供。等待时长必须小于task_timeout_ms;总时限包含等待时间。start_deadline_at不能早于计算后的available_at。所有时间规则及400/409错误见[任务接口](../api/tasks.md)。
|
||||
|
||||
服务端停机、队列暂停、没有兼容Worker或额度不足都可能让开始时间晚于available_at;到期后才可领取,不承诺准点执行。自动失败重试使用retry中的退避策略,不再次应用初始delay_ms。
|
||||
|
||||
## 失败与持久化边界
|
||||
|
||||
示例网络错误时非零退出,已打印任务ID可用于核对;不在同一次运行中换键重提。真实接入须在第一次请求前保存操作身份和请求,可使用[业务事务Outbox](task-outbox.md)。本例生成的新身份仅用于独立演示,重新运行不是上一次进程的恢复。
|
||||
|
||||
Outbox若使用delay_ms,等待从Orbit受理开始计算,不从本地业务事务或Outbox创建时间计算。需要固定业务时刻时冻结available_at;若发送时该时刻已超出接口容差,按拒绝结果核查,不静默改成“从现在起再等”。
|
||||
|
||||
旧服务不识别delay_ms。先更新服务端,再让调用方启用该字段;省略/零值保持旧请求摘要。计划模板拒绝非零delay_ms,时间由计划规则定义。人工重试的新开始时间使用其专门接口参数,不继承原任务的初始延迟。
|
||||
|
||||
`make integration`执行此例、真实HTTP/Worker延迟用例,以及重试退避期间全节点退出后的恢复测试;结果见[本轮报告](../reports/phase-3-delay-recovery.md)。
|
||||
@@ -172,3 +172,5 @@ send_count统计本地发送领取,与Pulse的执行尝试次数分别计数
|
||||
本例的业务效果仅为本地SQL记录及平台受理,不提供外部服务恰好一次执行。业务Worker继续承担业务幂等;执行结果需要[可靠结果订阅与Inbox](result-inbox.md)等衔接。敏感请求和回执留在业务数据库,应纳入业务访问控制、审计、备份及保留策略;日志只输出操作ID和稳定状态,不输出token或请求正文。
|
||||
|
||||
REVIEW后的人工核查、证据审批及受控恢复由接入方的业务管理流程承担,示例不提供无证据清空错误/重置预算接口。不要直接修改SQL状态制造成功,也不要在无法确认的情况下换业务操作ID。生产MySQL故障切换、长期归档、跨部署迁移和容量仍按P6验收;本例不启动流程或发送Weave信号,相应Outbox命令类型随流程API演进。
|
||||
|
||||
相对延迟任务可在SubmitTaskRequest设置DelayMS;Outbox冻结并重发原时长,由Orbit首次受理事务计算执行时间。它不从本地业务创建时间起算;固定业务时刻应使用冻结AvailableAt,详见[延迟接入](delayed-task.md)。
|
||||
|
||||
@@ -17,7 +17,7 @@ ORBIT_URL=http://127.0.0.1:8080 \
|
||||
|
||||
## 实际调用与结果
|
||||
|
||||
1. 提交两个完全相同业务键的任务,以及一个只相差尾部空格的任务。业务键含中文、加号、百分号及重音字符。
|
||||
1. 使用不同提交身份创建两个完全相同业务键、format分别为text/json的任务,以及一个业务键只相差尾部空格的任务。业务键含中文、加号、百分号及重音字符。读取前两个任务确认各自输入保留,改参数没有覆盖原任务。
|
||||
2. 用服务端回执中的CreatedAt设置包含起点、不包含终点的时间范围,避免依赖客户端时钟同步。
|
||||
3. ListTasks按原键、WAITING状态和创建区间查询,每页1条,沿NextCursor遍历;仅匹配前两个任务。
|
||||
4. 将created_before设为第二个任务的精确创建时间,确认它被排除,只剩第一个任务。
|
||||
@@ -26,11 +26,13 @@ ORBIT_URL=http://127.0.0.1:8080 \
|
||||
预期输出:
|
||||
|
||||
```text
|
||||
matched=2 pages=2 before_excluded=true cancelled=3
|
||||
matched=2 pages=2 before_excluded=true original_payload_preserved=true cancelled=3
|
||||
```
|
||||
|
||||
重复运行创建新的独立样本。代码没有外部业务副作用,也不删除数据库历史。失败非零退出,可能留下已受理任务;用回执、业务键和时间范围查询后核对,各任务总时限60秒,当前示例没有Worker会进入未开始超时处理。
|
||||
|
||||
本例通过business_key关联不同参数的任务,不表示覆盖、自动取消或自动代替原工作,来源仍为DIRECT。需要重新执行原冻结参数时使用带证据的[人工重试](../api/task-recovery.md);不能用新任务身份绕过未知业务效果核查。
|
||||
|
||||
完整参数、游标兼容和异常说明见[查询契约](../api/task-queries.md)。翻页时保持所有筛选,发生INVALID_ARGUMENT不要丢弃过滤条件“兜底查全部”。状态变化的跨页结果不是快照,不能当作完整审计导出。
|
||||
|
||||
`make integration` 会在一次性MySQL中运行此示例及实际HTTP SDK回归,验证原键/尾部空格、时间边界、分页、变更条件拒绝,以及取消后创建时间不改变。
|
||||
|
||||
Reference in New Issue
Block a user