feat: add idempotent batch task submission and recovery examples

This commit is contained in:
2026-09-17 21:21:18 +08:00
parent a8a8dcfe1e
commit 571107f96d
27 changed files with 1375 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
# Orbit HTTP 与 WebSocket 协议草案
> 版本 0.2 · 2026-09-17。HTTP + WS 已确认。本文的具体路径、消息名、字段及状态码是首版设计建议,业务协议尚未实现,需评审后冻结;健康接口的已实现契约见接口手册。
> 版本 0.2 · 2026-09-17。HTTP + WS 已确认。本文保留首版设计建议;已实现能力以[正式接口手册](api/README.md)为准,其余路径、消息名、字段和状态码仍待对应阶段实现及验证。
实现更新:HTTP 任务/队列接口已完成 P1;Worker 服务端已进入 P2,实际帧名、字段、回执与边界见 [Worker 契约](api/worker.md)。本文保留早期完整设计,未实现项目不能据此视为已交付。
@@ -18,7 +18,7 @@
| 方法与路径 | 行为 |
| --- | --- |
| `POST /v1/tasks` | 幂等提交一项任务 |
| `POST /v1/tasks:batchSubmit` | 批量逐项提交,每项独立幂等 |
| `POST /v1/tasks:batch` | 已实现;逐项身份、四类结果与限制见[批量手册](api/batch-tasks.md) |
| `GET /v1/tasks/{task_id}` | 查询权威任务状态与结果摘要 |
| `GET /v1/tasks/{task_id}/attempts` | 分页查询执行尝试 |
| `GET /v1/tasks` | 按有索引的字段分页查询,支持提交幂等键精确查找 |
@@ -55,7 +55,7 @@ Weave 的补充接口建议如下,功能范围已确认,具体技术细则
- 首次创建任务、流程或信号记录的建议响应为 `201 Created`,表示资源及幂等记录已可靠提交,不表示业务执行或信号消费完成。
- 相同幂等请求返回 `200` 和原资源 ID,标记 `deduplicated: true`;相同键不同语义请求返回 `409`。
- 单项提交使用 `Idempotency-Key`;批量每项使用 `submission_key`,由 SDK 统一映射为提交幂等标识。
- 单项提交使用 `Idempotency-Key`;已实现的批量每项使用 `idempotency_key`,与单项共用受理身份;不接受整批幂等头,见[批量接口](api/batch-tasks.md)。
- 普通任务提交幂等作用域为命名空间内的任务提交操作;流程启动使用独立操作作用域,信号按命名空间、实例与信号 ID 去重。其他管理写操作按命名空间、操作和资源界定作用域,避免同一个键在不同 API 间意外冲突。
- HTTP `request_id` 仅用于追踪,与业务幂等键分开。
- 请求超时后,使用原幂等键查询或重试;不能通过换一个键来猜测原请求是否失败。

View File

@@ -27,7 +27,7 @@
| P0 基础与数据模型 | Go workspace、独立 SDK module、公共/Pulse/Weave 模型、MySQL 迁移、配置、健康检查 | 编译/竞态/静态检查;真实 MySQL 建表、隔离、唯一性、预算、不可变版本、迁移失败防护;SDK 健康调用 | 已完成并通过阶段回归(见 P0 报告) |
| P1 任务受理闭环 | 应用凭据与作用域权限、队列、幂等提交、查询、待执行取消、受理事件;SDK 客户端 | API/SDK 创建查询闭环;重复键与内容冲突、响应未确认重报、跨命名空间拒绝、参数边界 | 已完成并通过阶段回归(见 P1 报告;真实断路/提交 ACK 丢失注入留后续故障阶段) |
| P2 Worker 执行闭环 | WSS 接入、能力/容量、领取与开始、租约、续租、结果确认、排空与恢复;Worker SDK | 真 Worker 执行;失联/重连/重复确认/旧尝试拒绝;业务效果未知转核查;节点故障接管 | 已完成并通过阶段回归:服务端、自动 SDK、历史查询、完整 Schema 与真实进程强杀恢复;测试拓扑和剩余生产故障矩阵见 P2 验收报告 |
| P3 Pulse 完整能力 | 安全重试、延时、Cron/固定间隔、漏跑策略、计划版本、运行中取消、并发许可、配额/背压、可靠结果订阅、批量受理、独立任务核查/人工重试、查询筛选与事务Outbox接入 | 多实例领取/触发竞争、重试上限、取消竞争、队列隔离、积压恢复、事件重复交付 | 进行中:安全重试、计划调度、运行中取消、队列控制/限速/积压背压、跨命名空间应用配额、显式资源键许可及 SDK 场景已实现并通过全量回归;优先级及节点公平派发已实现并通过全量回归;可靠订阅核心、HTTP/WSS、管理 SDK 和管理示例已实现并回归;消费 SDK、协议示例及两项节点强杀回归已完成;业务 Inbox 场景、COMMIT响应丢失及消费者进程故障回归已完成;批量受理、独立任务核查/人工重试、业务键/时间筛选与事务Outbox接入待完成 |
| P3 Pulse 完整能力 | 安全重试、延时、Cron/固定间隔、漏跑策略、计划版本、运行中取消、并发许可、配额/背压、可靠结果订阅、批量受理、独立任务核查/人工重试、查询筛选与事务Outbox接入 | 多实例领取/触发竞争、重试上限、取消竞争、队列隔离、积压恢复、事件重复交付 | 进行中:安全重试、计划调度、运行中取消、队列控制/限速/积压背压、跨命名空间应用配额、显式资源键许可及 SDK 场景已实现并通过全量回归;优先级及节点公平派发已实现并通过全量回归;可靠订阅核心、HTTP/WSS、管理 SDK 和管理示例已实现并回归;消费 SDK、协议示例及两项节点强杀回归已完成;业务 Inbox 场景、COMMIT响应丢失及消费者进程故障回归已完成;批量受理、SDK、接口与部分补交示例已完成全量回归;独立任务核查/人工重试、业务键/时间筛选与事务Outbox接入待完成 |
| P4 Weave 主流程 | SDK 定义构建与发布校验、实例、顺序/分支/结构化并行汇合、信号/定时等待、版本绑定 | 重启接续、早到/重复信号、并行竞争、内部事件重放、发布版本兼容;端到端通用流程 | 待开发 |
| P5 Weave 恢复与查询 | 有界查询、暂停继续、取消、人工核查恢复、控制门控与审计 | 轮次与尝试独立预算;暂停/开始/回执/超时竞争;恢复保留预算;未知结果不盲目重复 | 待开发 |
| P6 生产验证与运维 | 完整监控、凭据轮换、备份恢复、滚动升级、运行手册、可复现负载和故障工具 | 对照生产验收清单执行,报告环境/指标/遗留项;容量、RPO/RTO 按真实拓扑验证 | 待开发 |
@@ -37,11 +37,11 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
### P3 功能范围核对
可靠订阅链路完成后,重新对照 [原功能范围](01-functional-spec.md) 和 [事务衔接设计](04-reliability-and-data.md),以下原定首版能力仍有缺口。不能只按已实现模块反推P3完成;先补齐并回归,再进入完整阶段验收。
可靠订阅链路完成后,重新对照 [原功能范围](01-functional-spec.md) 和 [事务衔接设计](04-reliability-and-data.md),以下原定首版能力按核对结果逐项补齐。不能只按已实现模块反推P3完成;全部补齐并回归后,再进入完整阶段验收。
| 剩余项 | 当前证据与后续交付 |
| 核对项 | 当前证据与后续交付 |
| --- | --- |
| 批量任务受理 | [任务API](api/tasks.md) 明确尚未提供;需有界逐项结果、独立幂等、部分失败补交、SDK与运行示例 |
| 批量任务受理 | 已实现[批量API](api/batch-tasks.md)、独立SDK及[补交示例](examples/batch.md);真实MySQL部分失败与HTTP响应丢失用例已通过,同键并发20轮及全量回归已通过,见[报告](reports/phase-3-batch-tasks.md) |
| 独立任务人工核查与重试 | [重试API](api/retries.md) 仍未提供解除不确定性的核查接口;需证据、权限、版本、审计和历史保留,人工重试身份按关联新任务实现并冻结契约 |
| 任务查询筛选 | [当前ListTasks](../sdk/go/types.go) 仅队列/状态/分页;需按业务关联键及创建时间范围查询,游标绑定所有筛选,保持授权与有界索引扫描 |
| 业务到平台的事务Outbox接入 | [事务Outbox设计](04-reliability-and-data.md#61-业务系统到平台) 尚缺完整接入示例;需业务变更与待发请求同事务、稳定提交身份、响应不明核对和发送恢复,保持核心SDK无ORM/数据库依赖 |
@@ -63,6 +63,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
| 跨队列共享资源 | 两队列同键 → 一个有效许可 → 递增序号 → 资源端拒绝旧写入 | P3,已实现完整示例 |
| 优先级与应用隔离 | 近同时到期按优先级 → 旧任务等待补偿 → 应用/队列轮转 | P3,已实现完整示例 |
| 可靠结果订阅 | 冻结结果 → 持久化交付 → Inbox 去重 → 确认/补发 → 耗尽后人工重试 | P3,核心、HTTP/WSS、管理及消费 SDK、独立Inbox示例与真实进程故障回归已完成 |
| 批量文件作业受理 | 逐项身份 → 部分结果 → 同键核对 → 释放额度 → 只补交失败项 | P3,已实现完整示例 |
| 并行生成结果 | 发布流程 → 并行执行 → 汇合 → 完成 | P4 |
| 审核与外部回执 | 提交流程 → 等待信号 → 授权信号注入 → 继续 | P4 |
| 外部作业轮询 | 提交一次 → 有界查询处理中状态 → 回执或最终结果 → 超限核查 | P5 |
@@ -94,6 +95,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
- [P3 订阅通信与管理 SDK 回归](reports/phase-3-result-subscription-transport.md)
- [P3 消费 SDK 与节点故障回归](reports/phase-3-event-consumer-sdk.md)
- [P3 业务 Inbox 与消费者故障回归](reports/phase-3-result-inbox.md)
- [P3 批量任务受理回归](reports/phase-3-batch-tasks.md)
- [接口手册](api/README.md)
- [SDK 接入](sdk/README.md)
- [通用场景示例](examples/README.md)

View File

@@ -2,7 +2,7 @@
- 文档版本:0.2
- 整理日期:2026-09-17
- 阶段:P0–P2 已通过阶段验收;P3 安全重试、计划调度、运行中取消、队列资源控制和应用共享配额已通过回归,资源键许可及真实进程恢复已通过全量回归,优先级/节点公平派发及真实隔离场景已通过全量回归,数据库已改为无外键,P3–P7 继续推进,尚未完成全平台生产验收
- 阶段:P0–P2 已通过阶段验收;P3 安全重试、计划调度、运行中取消、队列资源控制和应用共享配额已通过回归,资源键许可及真实进程恢复已通过全量回归,优先级/节点公平派发及真实隔离场景已通过全量回归,批量任务受理已实现,数据库已改为无外键,P3–P7 继续推进,尚未完成全平台生产验收
- 适用范围:Orbit 统一平台,包含 Pulse 任务调度模块、Weave 流程编排模块与公共能力。平台面向多个业务系统复用。
## 阅读顺序

View File

@@ -9,6 +9,7 @@
| `GET /health/live` | 已实现 | [健康检查](health.md) |
| `GET /health/ready` | 已实现,验证数据库、迁移记录及零外键要求 | [健康检查](health.md) |
| `/v1/tasks` 提交、查询、分页与待执行取消 | P1 已实现 | [任务接口](tasks.md) |
| `POST /v1/tasks:batch` | P3 已实现逐项受理、独立幂等、部分结果与补交 | [批量任务](batch-tasks.md) |
| `/v1/tasks/{task_id}/attempts` 与尝试详情 | P2 已实现 | [执行历史](execution-history.md) |
| `/v1/tasks/{task_id}/attempts/{attempt_id}/evidence` 与证据详情 | P2 已实现 | [执行历史](execution-history.md) |
| `/v1/queues` 创建、查询及 `:configure` 控制 | 已实现队列并发、开始速率、积压背压与暂停/恢复 | [队列接口与 SDK 场景](queues.md) |

160
docs/api/batch-tasks.md Normal file
View File

@@ -0,0 +1,160 @@
# 批量任务受理 v1
状态:P3 已实现。`POST /v1/tasks:batch` 与 SDK `SubmitTasks` 提交一组独立任务,返回每项的受理结果。它不提供流程顺序、整批原子事务或业务效果恰好一次保证;步骤依赖由后续 Weave 管理。
## 1. 请求与权限
请求头必须有 `Authorization: Bearer <token>` 与 `Content-Type: application/json`。**不传 `Idempotency-Key` 请求头**,传入会返回400;每项在正文使用自己的 `idempotency_key`。凭据必须具备 `tasks:submit`。预认证后,每项写入仍在自己的事务内重新校验凭据、动作、目标队列范围和命名空间归属。
| 字段 | 类型与约束 |
| --- | --- |
| items | 数组,1至32项,保持输入顺序处理 |
| items[].idempotency_key | 1至128个ASCII可打印非空白字符,同一批内不能重复;调用方发送前持久化 |
| items[].task | 与 [POST /v1/tasks](tasks.md#post-v1tasks) 完全相同的任务对象、默认值及归一化规则 |
总请求不超过512 KiB,每项payload仍不超过256 KiB。服务端请求上下文最多8秒;调用方可能更早取消。SDK不自动拆批或并发发送,32项是保护边界,不是吞吐承诺。不要通过增加批次大小规避背压。
示意请求(将队列ID替换为初始化结果):
```json
{
"items": [
{
"idempotency_key": "conversion-001",
"task": {
"queue_id": "11111111111111111111111111111111",
"handler": "file.convert",
"handler_version": "v1",
"payload_version": "v1",
"payload": {"source_ref": "objects/input-001.txt", "sequence": 9007199254740993}
}
},
{
"idempotency_key": "conversion-002",
"task": {
"queue_id": "11111111111111111111111111111111",
"handler": "file.convert",
"handler_version": "v1",
"payload_version": "v1",
"payload": {"source_ref": "objects/input-002.txt"}
}
}
]
}
```
保存为 `batch.json` 后调用:
```sh
curl -i "$ORBIT_URL/v1/tasks:batch" \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @batch.json
```
## 2. 响应与逐项状态
整批格式与初始权限检查通过后返回HTTP200,即使其中部分或全部项目被拒绝。必须读取 `data.items`,不能只依据HTTP状态判断受理成功。每个输入恰好对应一条结果,`index` 为**本次请求**内从0开始的索引。只补交子集时索引重新编号,稳定身份仍由原幂等键确定。
| status | 字段 | 本次请求的含义与处理 |
| --- | --- | --- |
| ACCEPTED | receipt,无error | 已确认受理事务提交或核对到原回执;任务执行结果另查或订阅 |
| REJECTED | error,无receipt | 本项明确拒绝,不因本次调用新增任务;需按错误原因处理,既有同键任务可能仍存在 |
| UNKNOWN | error.code=UNAVAILABLE | 无法确认提交结果,包括数据库/提交响应异常;必须用原键和原参数核对 |
| NOT_ATTEMPTED | error.code=BATCH_STOPPED | 本请求未尝试该项;之前其他请求可能已受理,仍须沿用原身份 |
例如第一项受理、第二项队列满额(ID和时间仅作示意):
```json
{
"request_id": "44444444444444444444444444444444",
"data": {
"items": [
{
"index": 0,
"status": "ACCEPTED",
"receipt": {
"task_id": "22222222222222222222222222222222",
"queue_id": "11111111111111111111111111111111",
"revision": "1",
"created_at": "2026-09-17T00:00:00Z",
"deduplicated": false
}
},
{
"index": 1,
"status": "REJECTED",
"error": {
"code": "QUEUE_FULL",
"message": "queue unfinished task limit reached; retry later using the original identity",
"retry_after_ms": 1000
}
}
]
}
}
```
`receipt` 是原始受理回执的精简视图,字段如下:
| 字段 | 类型与含义 |
| --- | --- |
| task_id / queue_id | 32位小写十六进制标识 |
| revision | 十进制字符串,原受理版本;任务已执行或取消后重报仍返回原版本 |
| created_at | 原任务创建时间,UTC RFC3339微秒精度 |
| deduplicated | bool,本项是否复用已有受理回执 |
回执不重复携带payload、结果或策略,32项返回大小有界。需要当前完整状态时调用 `GetTask`;取消、核查等后续操作应先查询当前版本。
## 3. 整批拒绝与部分失败
开始处理任何项目之前,完整读取并严格解析整个请求。以下情况整批返回错误信封,当前请求不写任务:
- HTTP400:空批/超过32项、无效或重复的项幂等键、传整批幂等头、未知字段/大小写别名/重复JSON键、字段类型或ID/时间编码错误、非法JSON/Unicode、非可空字段为null。
- HTTP413:总请求超过512 KiB;HTTP415:Content-Type不支持。
- HTTP401/403:初始凭据无效或无tasks:submit动作权限。
- HTTP503:预认证数据库不可用;此时还未进入逐项受理。
结构正确后,任务语义错误按项返回 `REJECTED/INVALID_ARGUMENT`,例如处理器为空、优先级超过9、有效时间范围不满足要求。队列不存在或跨命名空间为NOT_FOUND,缺少队列范围为PERMISSION_DENIED,同身份不同内容为IDEMPOTENCY_CONFLICT。队列或应用未终结上限分别为QUEUE_FULL、APPLICATION_FULL,按项提供1000毫秒退避提示;HTTP200不附整批Retry-After头。
处理遇到未知数据库结果后立即停止余项:当前项UNKNOWN,余项NOT_ATTEMPTED。中途凭据失效时当前项REJECTED/UNAUTHENTICATED,余项停止。上下文取消时,已确认提交的前项保留ACCEPTED,尚未开始的后项NOT_ATTEMPTED;在途项若无法确认提交则UNKNOWN。响应本身也可能丢失,不能依赖一定能收到这些状态。
一项队列越权、语义错误、幂等冲突或额度不足,不阻止后续独立项尝试。不在请求之间保存批次游标或整批快照,也不撤销此前已经受理的项目。每项任务、幂等记录、额度、事件与审计继续同事务提交;没有新增数据库表、外键或级联操作。
## 4. 幂等与补交
1. 在业务自己的事务中保存每项稳定键和完整请求内容,再发给平台。业务变更与平台受理仍有跨系统事务边界,后续事务Outbox示例单独交付。
2. 批量与单项提交使用相同的 `task.submit` 命名空间身份。批量受理后用同键调用SubmitTask会去重;反方向相同。换批次、重排或只补交子集不会改变身份。
3. 原回执核对先于当前积压额度检查,队列满额时仍可获取已受理项目的回执;当前凭据与队列授权仍须有效。
4. UNKNOWN、超时、连接断开或无法验证的响应:保留原键、原参数和原相对时限;不要重算available_at,不生成新键。不确定哪些项成功时可重报整批。
5. 明确额度不足时,退避并释放/等待配额后,可只补交被拒项目,仍用原身份。参数错误需要修正时先确认该操作从未受理,不能把“换键”当作解决幂等冲突的方法。
6. 幂等记录当前标记90天过期但尚无在线清理;生产清理窗口按后续保留策略执行,业务不得假定无限去重。
队列暂停仍允许受理。暂停、取消和任务执行与批量接口独立,提交顺序不保证执行顺序。业务效果去重仍由Worker/接收方负责。
## 5. Go SDK
```go
// items 来自业务库中的持久化待发记录。
result, err := client.SubmitTasks(ctx, orbit.SubmitTasksRequest{Items: items})
if err != nil {
// 网络/响应错误无法证明未提交;保留所有原身份,稍后核对。
return err
}
for _, item := range result.Items {
switch item.Status {
case orbit.BatchAccepted:
// 将task_id与对应的持久化操作关联;此处不代表业务执行成功。
fmt.Println(item.Index, item.Receipt.TaskID, item.Receipt.Deduplicated)
case orbit.BatchRejected:
// 根据item.Error.Code决定修正、人工处理或按RetryAfterMS退避。
fmt.Println(item.Index, item.Error.Code)
case orbit.BatchUnknown, orbit.BatchNotAttempted:
// 使用items[item.Index]的原键和原参数保留待核对状态。
}
}
```
SDK验证输入数量/身份/整体体积及返回数量、顺序、状态和回执归属;不自动重试、拆批或创建新键。`err == nil` 只表示取得可解释的逐项响应。局部错误为 `BatchItemError`,不是顶层APIError;整批HTTP错误仍为APIError,网络错误沿用标准库。独立SDK无数据库依赖。
完整运行命令、输入输出和收尾见 [批量场景](../examples/batch.md),源码见 [SDK示例](../../sdk/go/examples/batch/main.go),覆盖范围和实际证据见 [回归报告](../reports/phase-3-batch-tasks.md)。旧客户端继续使用单项接口;新SDK调用旧服务该路由会返回404,不会静默退化为其他语义。

View File

@@ -6,7 +6,7 @@ P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生
## 公共约定
业务接口需要 `Authorization: Bearer <token>`;写入需要 `Content-Type: application/json` 和稳定的 `Idempotency-Key`。凭据决定命名空间,请求体不能覆盖它。详见 [鉴权与初始化](authentication.md)。
业务接口需要 `Authorization: Bearer <token>`;单项写入需要 `Content-Type: application/json` 和稳定的 `Idempotency-Key`;[批量受理](batch-tasks.md) 在每项正文提供独立幂等键。凭据决定命名空间,请求体不能覆盖它。详见 [鉴权与初始化](authentication.md)。
请求体上限 512 KiB。拒绝未知字段、大小写别名、重复 JSON 键、无效 UTF-8、孤立 Unicode 代理项和超过 32 层的嵌套。除可空时间外,字段不能显式传 null。请求追踪 ID 由服务端生成,与幂等键分开。
@@ -190,4 +190,4 @@ SDK:`Client.CancelTask(ctx, taskID, key, request)`。此操作不承诺回滚
{"request_id":"44444444444444444444444444444444","error":{"code":"IDEMPOTENCY_CONFLICT","message":"idempotency key was used with different content"}}
```
有限自动重试已实现,具体分类、退避、回执和升级边界见 [安全重试](retries.md)。[运行中取消](cancellation.md) 已实现;批量受理和人工重试尚未实现。[执行历史](execution-history.md) 已提供尝试与证据的列表和详情。完整可运行程序见 [SDK 任务示例](../../sdk/go/examples/tasks/main.go),已验证异常场景见 [P1 回归报告](../reports/phase-1.md) 及 [取消回归](../reports/phase-3-cancellation.md)。SDK 忽略未知响应字段;更改已有字段语义需升级契约和回归。
有限自动重试已实现,具体分类、退避、回执和升级边界见 [安全重试](retries.md)。[运行中取消](cancellation.md) 已实现;[批量受理](batch-tasks.md) 已实现逐项结果与原身份补交;独立任务人工重试仍待实现。[执行历史](execution-history.md) 已提供尝试与证据的列表和详情。完整可运行程序见 [SDK 任务示例](../../sdk/go/examples/tasks/main.go),已验证异常场景见 [P1 回归报告](../reports/phase-1.md) 及 [取消回归](../reports/phase-3-cancellation.md)。SDK 忽略未知响应字段;更改已有字段语义需升级契约和回归。

View File

@@ -130,3 +130,11 @@
- 输出:inbox_duplicate=true、applied_events=1、ack_confirmed=1、pending=0。
- 覆盖:并发重复、旧版本保护、回滚、真实MySQL COMMIT响应丢失、业务提交后/ACK前消费者SIGKILL及新进程去重。
- 边界:只覆盖接收方本地SQL效果;核心SDK不依赖数据库,外部副作用需要接收方自己的幂等/Outbox设计。
## 已可运行:批量受理与部分补交
- 源码:[batch](../../sdk/go/examples/batch/main.go),[完整启动命令和输出](batch.md)。
- 步骤:独立暂停队列 → 三项批量提交、两项成功一项满额 → 整批核对原回执 → 取消释放额度 → 仅原身份补交第三项 → 取消收尾。
- 预期:accepted=2、replayed=2、retried_items=1、unfinished=0。
- 异常:逐项区分REJECTED/UNKNOWN/NOT_ATTEMPTED;真实HTTP响应丢失后恢复无重复,保留当前权限和额度控制。
- 范围:示例只演示受理,不转换文件、不提供业务Outbox;失败非零退出,每项60秒总时限,保留队列及审计。

50
docs/examples/batch.md Normal file
View File

@@ -0,0 +1,50 @@
# 批量文件作业受理与部分补交
[完整源码](../../sdk/go/examples/batch/main.go) 使用独立Go SDK,不导入服务端模型或数据库驱动。它演示受理与取消,不启动Worker或转换真实文件。
## 前置与运行
使用已迁移的MySQL和当前服务。开发凭据需要 `queues:manage/read`、`tasks:submit/read/cancel` 和当前命名空间全部队列范围;标准bootstrap默认具备这些权限。只在专用开发命名空间运行。
仓库根目录中,先通过环境配置专用 `ORBIT_MYSQL_DSN`:
```sh
go run ./cmd/orbit-migrate up
mkdir -p .local
chmod 700 .local
go run ./cmd/orbit-admin bootstrap \
--application batch-demo --namespace default --environment development \
--queue files --output .local/batch.credentials.json
go run ./cmd/orbit
```
另开终端:
```sh
cd sdk/go
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=../../.local/batch.credentials.json \
go run ./examples/batch
```
也接受ORBIT_TOKEN,环境值优先。示例自行创建并暂停一个独立队列,未终结上限为2,不改变已有队列或应用配置。前置应用剩余额度至少2。
## 调用链与输出
1. 三项文件引用与精确整数输入,使用三个稳定键一次提交。
2. 前两项受理,第三项QUEUE_FULL;整批重报确认前两项ID不变且去重。
3. 查询并取消第一项释放额度,仅以第三项原键和原参数补交。
4. 取消剩余两项,核对unfinished=0,队列保持暂停。
```text
accepted=2 rejected=1 code=QUEUE_FULL
replayed=2 same_task_ids=true
retried_items=1 same_identity=true accepted=true
queue=<实际32位ID> paused=true unfinished=0
```
每项总时限60秒,示例总调用上下文20秒。所有响应都校验,失败非零退出且不打印token或业务参数。网络异常时示例直接报错,不循环生成新身份;真正生产发送器应从业务库恢复原身份再核对。示例每次运行生成独立身份,**不是持久化业务Outbox实现**。
正常完成保留已取消任务、队列和审计,不硬删除。异常中断可能留下任务;恢复扫描器按总时限处理,示例不能保证强杀后的主动清理。对有外部副作用的真实任务仍需核查执行状态。
[真实SDK回归](../../sdk/go/batch_integration_test.go) 还通过HTTP代理在服务已返回受理回执后强制断线,再用原身份重报,确认没有重复任务。全部失败分类、部分提交与单项/批量互通见 [接口手册](../api/batch-tasks.md)。

View File

@@ -0,0 +1,95 @@
# P3 批量任务受理回归
验证日期:2026-09-17。本阶段增加批量受理、SDK及部分补交场景;不代表P3或全平台验收完成。完整集成已通过,下面记录实际覆盖范围与剩余边界。
## 1. 实现与证据入口
- [服务契约](../../pulse/service/batch.go):最多32项,逐项独立事务与幂等,ACCEPTED/REJECTED/UNKNOWN/NOT_ATTEMPTED四类结果。未知数据库结果或凭据失效后停止余项,上下文结束保留已确认前项。
- [HTTP入口](../../internal/transport/httpserver/tasks.go):`POST /v1/tasks:batch`,全体正文严格解析后才受理,每项正文幂等键,总请求512 KiB。完整响应使用HTTP200,逐项标记结果。
- [独立SDK](../../sdk/go/batch.go):SubmitTasks及中文字段说明,不自动生成身份、拆批或重试;校验返回数量、顺序、状态与回执归属。
- [运行示例](../../sdk/go/examples/batch/main.go):两项受理、一项满额,原键核对后释放配额,仅补交被拒项并取消收尾。
- [接口手册](../api/batch-tasks.md)、[场景说明](../examples/batch.md)、[开发计划](../08-development-plan.md) 同步维护。
不增加数据库模型或迁移,平台仍为35个实体、289个中文注释持久化字段;不增加外键、级联或SDK数据库依赖。
## 2. 验证环境与命令
macOS、Go1.27.1、Docker单实例MySQL8.4.10。使用一次性数据库、随机测试凭据及回环端口,结束后自动删除,不修改已有业务库。
```sh
make fmt test vet build
make integration
```
普通测试没有数据库环境时会跳过Integration;数据库/HTTP/进程证据来自真实集成入口。并发修复另在一次性数据库重复执行:
```sh
go test -race -count=20 \
-run '^TestIntegrationTaskBatch/concurrent_replay_and_shared_application_backpressure$' \
-v ./internal/storage/mysql
```
该定向命令要求 `ORBIT_TEST_MYSQL_DSN` 指向一次性数据库;正式完整脚本会自动准备隔离环境。
| 验证组 | 实际结果 |
| --- | --- |
| fmt / race / vet / build | 已通过 |
| 定向批量MySQL六组场景 | PASS,5.559秒,含包开销 |
| 真实HTTP丢失批量响应及SDK补交 | PASS,1.458秒,含包开销 |
| 完整批量示例 | 已通过,unfinished=0 |
| 同键并发与共享配额20次重复 | PASS,10.110秒;每轮8个调用者 |
| 全量MySQL集成 | PASS,94.874秒 |
| SDK Worker完整组 | PASS,88.099秒 |
| SDK调度 / 队列资源 / 批量受理 | PASS,5.640 / 4.474 / 1.361秒 |
| SDK订阅管理 / 事件消费 | PASS,33.508 / 51.852秒 |
| Inbox六组真实数据库/消费者故障 | PASS,35.783秒 |
| SDK应用配额 | PASS,1.426秒 |
| 九项节点/Worker进程故障 | PASS,69.057秒;连同Inbox消费者共十项真实进程故障 |
| 全部运行示例 | 15个核心SDK示例 + 1个独立Inbox示例通过 |
上述完整脚本正常退出,状态码0;没有以尚在运行的日志推断完成。文档链接与JSON代码块核对通过,核心SDK依赖检查未包含服务端模型或MySQL驱动。以上是单次回归耗时,不是吞吐或性能指标。
## 3. 事务、权限与配额
[数据库回归](../../internal/storage/mysql/task_batch_integration_test.go) 实际验证:
1. 有效/参数无效/不存在队列/队列满额混合批次,每项结果独立;数据库仅有两份任务、受理幂等、事件及审计。
2. 单项与批量共用原始回执;取消后重报仍返回原受理版本,满额也不阻止已提交身份核对。释放配额后原失败身份受理成功,同键不同内容仍冲突。
3. 跨命名空间队列与队列权限拒绝;即使已有回执也不能绕过当前队列范围。
4. 第一项提交后撤销凭据,第二项重新认证拒绝,第三项未尝试;只有第一项任务存在。
5. 第一项提交后取消请求上下文,后缀未写入;恢复后整批原键重报,首项去重、后缀受理。
6. 真实审计触发器故障使新项事务回滚,任务/幂等/事件/配额不残留;既有已受理项保留。接口保守返回UNKNOWN,修复故障后原身份补交成功。
7. 八个调用者同时重报四个相同身份,应用上限3;前三个稳定任务ID、仅三次新受理,第四项APPLICATION_FULL,另一命名空间也受共享额度限制。
单元及HTTP边界还覆盖整批非法键/重复键/超量、全项参数无效时仍认证、未知字段/大小写别名/重复JSON键/null、超大正文、禁止整批幂等头、精确整数、不完整或矛盾的SDK回执,以及上下文和内部错误不泄露细节。
## 4. 并发回归发现与修复
最初同键并发满额场景实际出现MySQL 1213死锁,API按既有保守规则报告UNKNOWN。测试没有将UNKNOWN改判成功,也没有通过放宽断言隐藏问题。
任务提交原来先抢占幂等唯一键,再等待应用额度锁。在满额回滚和同键INSERT竞争中触发锁冲突。本轮调整为:认证 → 应用额度锁 → 提交幂等身份 → 队列 → 任务/事件/审计。取得应用锁时不检查或扣减额度;读取已有回执仍在配额检查之前。
提取只在应用锁已持有时使用的队列锁辅助函数,避免重复查询应用锁。每项事务结束后释放全部锁,整个批次不持有跨项锁。没有新增外部锁服务、数据库外键或盲目重放事务。当前场景通过不表示全部MySQL负载不再可能死锁;其他未知数据库错误仍必须原身份核对。
## 5. 真实HTTP故障与示例
[SDK集成](../../sdk/go/batch_integration_test.go) 启动真实HTTP代理:先读完平台已经生成的受理响应,再强制关闭客户端连接、丢弃响应。SDK返回网络错误,未自动补发。再次用原键/原参数请求,两项返回同一任务且deduplicated=true,第三项保持QUEUE_FULL;查询仅存在两行任务。取消一项后只补交原第三项,受理成功。
完整示例输出:
```text
accepted=2 rejected=1 code=QUEUE_FULL
replayed=2 same_task_ids=true
retried_items=1 same_identity=true accepted=true
queue=<实际ID> paused=true unfinished=0
```
这验证受理与HTTP回执恢复,不等同于Worker业务效果恰好一次,也不是业务事务Outbox。任务、队列和审计保留,无硬删除。
## 6. 兼容与尚未验收范围
- 无数据库迁移变更;批量与单项受理的身份、策略、事件和审计相同。新SDK调用旧节点返回404,不静默降级。
- 单项与批量均使用调整后的锁顺序。部署应统一升级写入节点;未做新旧二进制混合压力或滚动发布验收。
- 批次逐项串行,8秒HTTP预算不保证每次32项全部尝试;大参数回执不重复携带原输入。调用方必须处理部分提交和响应全丢。
- 本轮为本机单MySQL正确性验证,未验证数据库故障切换、跨区域网络、百万租户容量或生产RPO/RTO。
- P3仍缺独立任务人工核查/重试、业务键及时间筛选、事务Outbox示例;P4–P7仍按原目标推进。

View File

@@ -1,6 +1,6 @@
# Go SDK 接入说明
已实现健康查询、任务提交/查询/分页/待执行取消、队列创建/查询、[自动 Worker](worker-runtime.md)、[Worker 底层协议 SDK](worker.md) 和 [执行历史查询](../api/execution-history.md)。[安全重试](../api/retries.md) 已提供有限策略、WorkerFailure.Retryable、历史查询及完整例子。[计划调度](../api/schedules.md) 已支持创建、查询、版本发布、暂停/恢复、触发历史与完整示例。[订阅管理](../api/subscriptions.md) 已支持创建、查询、暂停恢复、交付历史与人工重试;[事件消费 SDK](event-consumer.md) 已支持连接、自动心跳、重连与确认。流程构建等后续接口按 [开发计划](../08-development-plan.md) 继续实现。
已实现健康查询、任务单项与[批量提交](../api/batch-tasks.md)/查询/分页/待执行取消、队列创建/查询、[自动 Worker](worker-runtime.md)、[Worker 底层协议 SDK](worker.md) 和 [执行历史查询](../api/execution-history.md)。[安全重试](../api/retries.md) 已提供有限策略、WorkerFailure.Retryable、历史查询及完整例子。[计划调度](../api/schedules.md) 已支持创建、查询、版本发布、暂停/恢复、触发历史与完整示例。[订阅管理](../api/subscriptions.md) 已支持创建、查询、暂停恢复、交付历史与人工重试;[事件消费 SDK](event-consumer.md) 已支持连接、自动心跳、重连与确认。流程构建等后续接口按 [开发计划](../08-development-plan.md) 继续实现。
module:`git.yuntuo.hk/k2safe/orbit/sdk/go`。SDK 使用独立 go.mod,依赖 Go 标准库与 `github.com/coder/websocket v1.8.15`,不引用服务端模型或数据库实现。
@@ -105,6 +105,7 @@ fmt.Println(cancelled.Task.State)
| 方法 | 请求 / 返回 | 文档 |
| --- | --- | --- |
| SubmitTasks | SubmitTasksRequest / SubmitTasksResult,32项独立幂等与结果 | [批量受理](../api/batch-tasks.md) / [运行场景](../examples/batch.md) |
| CreateSubscription / GetSubscription / ListSubscriptions | 订阅受理、当前配置与队列分页 | [订阅](../api/subscriptions.md) |
| ConfigureSubscription | 显式暂停/继续与版本、原因 | [订阅](../api/subscriptions.md) |
| ListEventDeliveries / GetEventDelivery / RetryEventDelivery | 交付历史、冻结结果和耗尽后追加预算 | [订阅](../api/subscriptions.md) |