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

8.9 KiB
Raw Blame History

批量任务受理 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 完全相同的任务对象、默认值及归一化规则

总请求不超过512 KiB,每项payload仍不超过256 KiB。服务端请求上下文最多8秒;调用方可能更早取消。SDK不自动拆批或并发发送,32项是保护边界,不是吞吐承诺。不要通过增加批次大小规避背压。

示意请求(将队列ID替换为初始化结果):

{
  "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 后调用:

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和时间仅作示意):

{
  "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

// 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无数据库依赖。

完整运行命令、输入输出和收尾见 批量场景,源码见 SDK示例,覆盖范围和实际证据见 回归报告。旧客户端继续使用单项接口;新SDK调用旧服务该路由会返回404,不会静默退化为其他语义。