Files
orbit/docs/api/batch-tasks.md

161 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 批量任务受理 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,不会静默退化为其他语义。