长时作业的异步与轮询
长时作业不适合用同步请求等待完成:HTTP 请求、反向代理连接、SSH 会话、脚本调用都各有超时,等待几十分钟的任务几乎必然在某个中间环节被切断,而应用日志里往往看不到错误。通用做法是异步化:提交时立即返回一个作业标识,真正的执行在后台进行,客户端凭标识轮询状态、获取结果或等待回调通知。设计要点是标识的生命周期、轮询间隔的退避、状态取值的明确定义,以及结果与日志的保留期。
基本信息
| 接口形态 | 提交返回 202 与作业标识(响应体字段或 Location 头),另有查询接口按标识返回状态 |
|---|---|
| 轮询策略 | 先密后疏,带退避与次数上限;也可用长轮询或回调替代 |
| 状态取值 | queued、running、succeeded、failed、timeout、canceled 等,需固定并向后兼容 |
| 保留期 | 结果与日志设置过期时间,避免存储无界增长 |
典型使用场景
- 大批量变更通过接口触发,客户端拿标识轮询进度
- 数据库备份与恢复等耗时较长的任务
- 跨系统调用中上游对单次请求有严格的超时限制
- 后台重任务需要在界面展示进度,而不是一直等待转圈
- 任务完成后回调通知下游系统开始后续处理
提交与轮询示例
提交与轮询示例
resp=$(curl -sS -X POST -H 'Content-Type: application/json' \
-d '{"action":"reindex"}' https://api.example.com/v1/jobs) # 提交,立即返回
id=$(printf '%s' "$resp" | jq -r '.job_id') # 取作业标识
for i in 1 2 3 5 8 13; do # 退避式轮询,避免高频
st=$(curl -sS "https://api.example.com/v1/jobs/$id" | jq -r '.status')
[ "$st" = "succeeded" ] || [ "$st" = "failed" ] && break
sleep "$i"
done
printf 'final=%s\n' "$st" # 结束时必须拿到终态异步作业要素
| 要素 | 做法 | 失配后果 |
|---|---|---|
| 作业标识 | 提交时生成并返回 | 没有标识就只能同步等待 |
| 轮询间隔 | 退避策略,先密后疏 | 固定高频轮询会打满查询接口 |
| 状态取值 | 固定枚举并向后兼容 | 客户端按字符串猜状态,升级即出错 |
| 保留期 | 结果与日志设置 TTL | 无限保留导致存储持续膨胀 |
| 回调 | 任务完成时通知下游 | 需处理重复回调与回调丢失 |
反向代理与网关都有自己的空闲超时,同步等待长任务的请求往往先被中间设备断开,而应用侧没有任何错误日志,排查时极易误判为业务异常。
轮询接口必须做限流与缓存,否则大量客户端在同一秒轮询会让查询接口先于业务接口崩溃。长轮询与回调能显著降低轮询压力,但需要额外处理连接数占用与回调重试幂等。