做一套作业提交与状态轮询的 API
目标
亲自实现以 202 Accepted 开始的异步 API 完整契约——接收、状态查询、轮询调节、幂等提交和完成通知。
为什么重要
如果把耗时 30 秒的任务做成同步 API,会同时破坏三件事:触发负载均衡器的空闲超时、客户端重试导致同一任务执行两次、工作进程每次被占用 30 秒。异步 API 将其拆分为接收和确认。然而,出乎意料的是,很多 API 只返回 202 就结束了。客户端不知道去哪里查看结果、不知道要等待多久,也不知道是否可以重试。本练习会逐一补齐契约中缺失的部分——Location 标头、状态词汇、Retry-After、幂等键以及 Webhook。全部具备之后,客户端才能放心地重试。
步骤
- 在 127.0.0.1:8140 启动
/root/aj/api.py。POST /jobs接收{"n":5}并将其放入队列q:jobs2。 POST /jobs响应状态为 202,包含Location: /jobs/<job_id>标头以及正文{"job_id":"...","status":"queued"}。GET /jobs/<job_id>返回 200 和status。其值为queued、running、succeeded、failed之一。不存在的 id 返回 404。/root/aj/worker.py从队列取出任务,将状态改为running,处理后再改为succeeded。- 已完成任务的查询响应中必须有
result键。处于queued状态时不得有result键。 queued或running状态的响应应带有以整数秒表示的Retry-After标头,而succeeded响应不应包含它。- 使用同一个
Idempotency-Key提交两次时,应返回同一个job_id,并且队列中只能有 1 条任务。 - 在 127.0.0.1:8141 启动
/opt/app/hooksink.py。提交时同时接收callback_url,任务完成后向该地址发送 POST。/root/aj/hook.log中应记录job_id和status=succeeded。
参考
- 202 表示的不是“已完成”,而是“已接收”。没有 Location,契约就不完整。
- 为已完成的任务状态设置 TTL,并在过期查询时返回 410 Gone 而不是 404,可以让客户端区分两种情况。
- 常见错误 1:把尚未创建的结果资源 ID 用作 job id——任务失败后会留下幽灵 ID。
- 常见错误 2:在完成响应中也附加
Retry-After,导致客户端永远持续轮询。
启动任务提交 API
在 127.0.0.1:8140 启动 /root/aj/api.py。POST /jobs 接收 {"n":5} 并将其放入队列 q:jobs2。
提交接口将任务放入队列后立即响应。不要实际处理任务——这正是异步的核心。
返回 202 和 Location 标头
POST /jobs 响应状态为 202,包含 Location: /jobs/<job_id> 标头以及正文 {"job_id":"...","status":"queued"}。
只有同时说明“已接收”和“在哪里查看”,契约才成立。标头和正文缺一不可。
创建状态查询端点
GET /jobs/<job_id> 返回 200 和 status。其值为 queued、running、succeeded、failed 之一。不存在的 id 返回 404。
四种状态词汇就足够了。对于不存在的 job id,请返回 404。
让工作进程推进状态
/root/aj/worker.py 从队列取出任务,将状态改为 running,处理后再改为 succeeded。
从队列取出后状态为 running,结束后为 succeeded。将状态存入 Redis 哈希会比较方便。
完成后加入结果
已完成任务的查询响应中必须有 result 键。处于 queued 状态时不得有 result 键。
如果结果较小,可以随状态响应一起返回。完成之前不得出现 result 键。
建议轮询间隔
queued 或 running 状态的响应应带有以整数秒表示的 Retry-After 标头,而 succeeded 响应不应包含它。
只在任务进行中提供该标头。如果已完成响应中仍有这个标头,客户端会继续轮询。
使提交具有幂等性
使用同一个 Idempotency-Key 提交两次时,应返回同一个 job_id,并且队列中只能有 1 条任务。
如果在收到 202 之前连接中断,客户端会重试。对同一个键请返回同一个标识符。
发送完成 Webhook
在 127.0.0.1:8141 启动 /opt/app/hooksink.py。提交时同时接收 callback_url,任务完成后向该地址发送 POST。/root/aj/hook.log 中应记录 job_id 和 status=succeeded。
先启动接收器,并在提交时同时接收回调地址。必须留下接收记录才能通过评分。