Skip to content

异步操作与作业资源

本章导读:视频转码、报表导出、AI 推理……任何"超过 1 秒"或"跨系统"的操作都不该让 HTTP 请求干等。本章给出 REST 处理长任务的完整模式:202 Accepted + 作业资源 + 三种进度获取方式(轮询/SSE/Webhook)——这是 REST 体系对"异步"的官方答案(RFC 9110 §9.3.3 + Google AIP-151 LRO 模式)。

1. 为什么需要专门模式

HTTP 请求-响应模型隐含两个假设:操作秒级完成、结果就在响应里。长任务打破两者:

  • 同步等待 → 线程被占死、网关 60s 超时、重试造成任务重复执行;
  • 直接返回 200 空跑 → 客户端不知道何时算完。

REST 的解法:把"一次执行"建模为资源。创建作业返回 202,之后所有状态都通过读取作业资源获得——HTTP 的"超时问题"被转化为"轮询一个秒回的资源"。

2. 模式骨架

http
# ① 提交任务(POST 非幂等 → 要求幂等键)
POST /v1/videos HTTP/1.1
Idempotency-Key: upload-20260918-01
Content-Type: application/json

{ "sourceUrl": "https://files.example.com/raw.mp4", "transcode": ["1080p", "720p"] }

# ② 立即受理
HTTP/1.1 202 Accepted
Location: /v1/jobs/501                  ← 作业资源位置
Content-Location: /v1/videos/9          ← 被创建资源(占位)的当前表示
Link: </v1/jobs/501>; rel="monitor"     ← RFC 8288:监视链接(语义标签)
Retry-After: 10                         ← 建议 10 秒后首次查询

# ③ 轮询作业
GET /v1/jobs/501
{ "id": "501", "type": "video.transcode", "status": "running",
  "progress": 42, ... }

# ④ 完成后再读业务资源
GET /v1/videos/9
{ "id": "9", "status": "ready", "playlists": [...] }

两个资源各司其职:作业(job)= 执行过程的快照;业务资源(video)= 结果。不要把"进度条"塞进业务资源字段。

3. 作业资源 Schema 设计

jsonc
{
  "id": "501",
  "type": "video.transcode",            // 任务类型(枚举,客户端按它解析 results)
  "status": "succeeded",                // 状态机:queued → running → succeeded | failed | cancelled | partialSuccess
  "progress": 100,                      // 0-100;不可估长时给 null + 心跳字段
  "priority": "high",                   // 可选
  "payload": { "videoId": "9" },        // 业务参数回显(便于排障)

  "result": {                           // 仅 succeeded 时存在:与同步端点同构的结果
    "created": ["/v1/videos/9/playlists/1080p", "/v1/videos/9/playlists/720p"]
  },
  "error": {                            // 仅 failed:RFC 9457 Problem
    "type": "https://api.example.com/problems/source-codec-unsupported",
    "title": "源视频编码不支持", "status": 422, "detail": "codec=prores"
  },

  "createdAt": "2026-09-18T08:00:00Z",
  "startedAt": "2026-09-18T08:00:03Z",
  "finishedAt": "2026-09-18T08:12:41Z",
  "expiresAt": "2026-10-18T08:12:41Z",  // 作业记录的保留期限
  "links": {
    "self":   "/v1/jobs/501",
    "cancel": "/v1/jobs/501:cancel",
    "retry":  "/v1/jobs/501:retry",
    "events": "/v1/jobs/501/events"     // SSE 端点
  }
}

设计纪律:

  1. 状态机封闭枚举,终态不可逆;非法迁移(对 succeeded 的作业发 cancel)→ 409
  2. result 与"同步版本端点"的返回同构——客户端完成后一次性读取即可,无需两套解析逻辑;
  3. error 直接复用 Problem Details——失败详情与同步失败同一形状;
  4. 作业保留期(expiresAt)后 404/410;文档声明——客户端应把结果落自己的库。

4. 进度获取的三种方式

方式端点形态优点代价
轮询(默认必选)GET /jobs/{id} + Retry-After简单、无状态、防火墙友好延迟 = 轮询间隔;空转流量
SSE 事件流GET /jobs/{id}/eventstext/event-stream实时、单向推送够用、走 HTTP长连接数管理;代理超时
Webhook 回调提交时给 callbackUrl,完成后 POST 通知服务端对服务端最省资源需验签、重试、乱序处理;公网可达要求

推荐组合:轮询保底 + SSE 增强。

4.1 轮询的节奏工程

  • 指数退避 + 抖动:1s → 2s → 4s → … 封顶 30s;服务器用 Retry-After 主动指导(比客户端猜强);
  • 批量场景用条件轮询GET /jobs?status=running&updatedAfter=…(一次看一批),或对作业集合开 GET /v1/jobs/changes?since=cursor 增量流(Delta 同步模式);
  • 作业响应带强 ETag + 支持 If-None-Match → 未变化时 304,轮询成本降到近乎零。

4.2 SSE 形状

http
GET /v1/jobs/501/events
Accept: text/event-stream

retry: 3000

event: progress
data: {"status":"running","progress":42}

event: progress
data: {"status":"running","progress":77}

event: done
data: {"status":"succeeded","result":{...}}

(服务器关闭连接;客户端用 Last-Event-ID 可恢复)

4.3 Webhook 的可靠性契约

json
POST {callbackUrl}
Content-Type: application/json
X-Webhook-Signature: t=1727251200,v1=5257a8…      ← HMAC 时间戳+签名(Stripe 式)
Idempotency-Key: job-501.succeeded.1               ← 同一事件可重投,接收方须去重

{ "id": "evt_9", "type": "job.succeeded", "createdAt": "…", "data": { "job": "/v1/jobs/501" } }
  • 期望接收方 2xx 确认;非 2xx/超时 → 指数退避重试(至少 24h),并文档化重试策略;
  • 通知只给"发生事件 + 资源链接",不搬运完整数据——让接收方回读 API(拉模式纠偏推送丢序);
  • 事件目录(job.failedarticle.published…)是对外契约,纳入版本治理。

5. Google AIP-151 参考实现(LRO)

大规模云 API 的成熟范式,值得借鉴其字段命名:

POST /v1/videos:transcode  →  200 { "name": "projects/p/locations/l/operations/501",
                                     "done": false, "metadata": { "progress": 0 } }
GET  /v1/{operation.name}  →  done=true 时 { "response": {...} } 或 { "error": {...} }

它的三要点——操作资源(operation)+ done 标志 + response/error 同位——即本章模式的标准答案变体。

6. 常见反模式

  1. 假异步:POST 同步干完 30 秒才返回 200——网关超时、重试风暴;
  2. 进度写进业务资源video.status=processing 42%video.status=ready 混一起——状态语义膨胀、轮询缓存互相污染;
  3. 无界作业表:不设置 expiresAt/归档,一年后 GET /jobs 全表扫描;
  4. Webhook 不带签名与幂等键:接收方要么被伪造事件打穿,要么重复消费炸库;
  5. 轮询风暴不设退避:千客户端 × 100ms 间隔 = 自己 DDoS 自己——用 Retry-After 与 304 控频。

7. 本章小结

  • 长任务 = POST202 + Location: /jobs/{id};结果、错误、进度、取消、重试全部资源化。
  • 作业 schema 三要素:状态机枚举、result 与同步端点同构、error 用 Problem。
  • 进度通道:轮询保底(ETag/304 + Retry-After),SSE 提体验,Webhook 走服务间——可叠加,签名与幂等不可省。

8. 下一步

平台级防护:限流头部与安全规范 → 限流与安全规范