LabHub
学习 学习路径 课程

队列与异步 API

202 Accepted 开启的那份契约

在 LabHub 中继续学习

一句话总结

202 表示的不是“已经完成”,而是“已经接收”。因此,返回 202 的 API 必须同时说明“到哪里查看结果”。

概念图: Location · Retry-After · 任务 ID · 重试与签名

为什么需要了解这些

把耗时 30 秒的任务设计成同步 API,会同时破坏三件事:触发负载均衡器的空闲超时(通常为 60 秒);客户端重试时,同一任务会执行两次;服务器 Worker 每次会被占用 30 秒。

异步 API 把它拆成两个阶段:提交与查询。提交快速结束并返回标识符;查询则使用该标识符获取状态。

工作原理

HTTP 已经为这种模式提供了现成的词汇。

提交响应使用 202 Accepted,并通过 Location 请求头返回状态查询 URL。响应正文中还要包含任务标识符和当前状态。缺少这三项,客户端就不知道下一步该做什么。

状态查询通过 200 返回当前状态。状态词汇越简单越好,例如 queuedrunningsucceededfailed。仍在进行时,通过 Retry-After 建议下一次轮询时间。缺少它,客户端会各自每秒轮询一次,状态 API 就会成为新的瓶颈。

任务完成后,有两种方式提供结果:把结果直接放入状态响应正文,或通过 303 See Other 指向结果资源 URL。后者更符合 REST 风格,但客户端实现更复杂。结果较小时,前者已经足够。

提交操作也必须具备幂等性。如果客户端在收到 202 之前连接中断并重试,同一任务就会创建两份。可以要求提交时提供幂等键,并为同一个键返回相同的 job id。

Webhook 是轮询的替代方案。任务完成时,服务器向客户端 URL 发起 POST。它没有轮询延迟,也不产生轮询负载,但同样需要付出代价:客户端必须提供公开端点;端点可能暂时不可用,因此服务器需要重试与 DLQ;还必须通过签名防止伪造。实际工作经常同时提供 Webhook 与轮询。

生产现场中的常见情况

最常见的设计错误,是直接把真实结果资源的 ID 用作 job id。这意味着提前提供尚未创建资源的 ID;如果任务失败,该 ID 就会永远成为幽灵。把 job id 与 result id 分开更安全。

第二,不要永久保存任务状态。为已完成状态设置 TTL;查询已过期任务时返回 410 Gone,而不是 404,这样客户端就能区分“从未存在”和“曾经存在但已过期”。

返回 202 后的契约

202 Accepted 只表示“已接收”,并不表示“必然成功”。因此,必须同时提供三项内容,客户端才能编写可靠代码。

HTTP/1.1 202 Accepted
Location: /api/jobs/7f3a-91cd
Retry-After: 3
Content-Type: application/json

{"job_id": "7f3a-91cd", "status": "queued",
 "poll_url": "/api/jobs/7f3a-91cd", "estimated_sec": 30}

状态查询至少应区分四种状态。

{"status": "queued"}                                  → 아직 시작 안 함
{"status": "running", "progress": 0.4}                → 진행 중
{"status": "succeeded", "result_url": "/files/…"}     → 결과가 있다
{"status": "failed", "error": {"code": "…","message": "…"}, "retriable": false}

retriable 很重要。客户端依据该值决定重新提交,还是要求人工处理。

用通知替代轮询

轮询简单,但会浪费资源。可以选择三种替代方案,每一种代价不同。

方式 服务器负担 客户端复杂度 防火墙
轮询 与请求数成正比 最简单 没有问题
长轮询 维持连接 简单 注意代理超时
SSE 维持连接 中等 通常可以通过
Webhook 最低 需要接收端点 对方必须有公开地址

使用 Webhook 时,必须同时设计重试与签名。接收方短暂宕机时需要重发;为了防止伪造,还应在正文上附加 HMAC 签名。

使用幂等键阻止重复提交

客户端在收到 202 前超时,会再次发送相同请求,从而创建两个任务。

POST /api/jobs
Idempotency-Key: 8f14e45f-ea3b-4d29-9a1c-2b3c4d5e6f70

服务器保存该键;再次收到相同键时,不创建新任务,而是原样返回首次创建任务的 202 响应。 键由客户端创建,但在多次重试间不能改变。因此,可以根据请求内容确定性地生成,或在请求开始时只生成一次并保存。

键的常见保留期限约为 24 小时。保留更久会让存储持续增长,过短则无法识别延迟较长的重试。

下一次实验要做什么

创建任务提交 API,准确返回 202 与 Location;让 Worker 推进任务状态;使用 Retry-After 调整轮询;使提交具备幂等性;最后添加完成 Webhook。