5.8 KiB
5.8 KiB
Init Execute Async Resume Design
Date: 2026-03-10
Goal
解决初始化执行接口同步阻塞导致的请求超时问题,并支持任务在失败后从第一个未成功步骤继续执行,而不是每次都从头开始。
Scope
本设计覆盖:
/api/v1/init/tasks/{taskId}/execute改为异步受理- 初始化任务后台执行模型
- 步骤级断点续跑规则
- 同一任务的重复触发保护
- 现有查询接口与状态语义调整
本设计不覆盖:
- 分布式任务调度
- 多实例间执行协调
- 服务重启后的自动恢复调度
- 新增前端页面
- 初始化步骤编排本身的变更
Current State
当前执行入口是同步的:
- 控制器直接调用
InitService.executeTask(taskId) - 服务层在请求线程内串行执行全部步骤
- 任一步骤失败后立刻中断
当前问题有两个:
- 任务执行时间较长时,HTTP 请求容易超时
- 再次执行时没有跳过已成功步骤的逻辑,会从第一步重新开始
当前已有可复用基础:
- 任务主表
tms_init_task - 任务步骤表
tms_init_task_step - 步骤状态字段:
PENDING/RUNNING/SUCCESS/FAILED - 现有步骤详情和日志查询接口
Design Choice
采用:应用内异步执行 + 基于步骤状态续跑
不采用:继续同步执行
原因:
- 无法避免请求超时
- 与实际长耗时初始化场景不匹配
不采用:数据库任务队列 + 独立 worker
原因:
- 当前需求重点是先解决超时和重跑问题
- 现阶段引入完整调度系统成本过高
API Design
Execute API
POST /api/v1/init/tasks/{taskId}/execute
接口语义调整为:
- 只负责受理执行请求
- 不等待全部步骤完成
- 返回当前任务快照
返回字段继续复用 InitTaskExecuteResponse,但语义变为:
taskId: 任务号status: 受理后的当前状态,通常为RUNNINGtotalSteps: 总步骤数successSteps: 当前已成功步骤数
前端后续继续通过以下接口轮询:
GET /api/v1/init/tasks/{taskId}GET /api/v1/init/tasks/{taskId}/stepsGET /api/v1/init/tasks/{taskId}/steps/{stepNo}/log
Execution Model
1. Request thread
请求线程只做以下事情:
- 校验任务存在且有步骤
- 检查是否已经在执行中
- 将任务状态置为
RUNNING - 提交后台执行任务
- 立即返回
2. Background worker
后台线程负责真正串行执行步骤:
- 重新加载任务与步骤
- 识别续跑起点
- 逐步执行
- 每步结束后立刻落库状态、消息、日志路径、退出码
- 汇总任务最终状态
3. Single-task guard
同一 taskId 在一个应用实例内只允许一个执行线程:
- 若任务已在本实例执行中,再次调用
execute不再重复启动 - 接口直接返回当前快照
实现上可使用进程内 ConcurrentHashMap<String, Future<?>> 或等价结构维护活动任务。
Resume Rule
再次执行时采用以下规则:
- 所有
SUCCESS步骤直接跳过,不重跑 - 从第一个非
SUCCESS步骤开始继续 - 后续步骤按原顺序执行
- 若本次再次失败,当前失败步骤状态记为
FAILED,任务状态记为FAILED
RUNNING residue handling
如果上一次执行过程中服务异常退出,可能残留 RUNNING 步骤。
本设计约定:
- 新一轮执行开始前,将残留
RUNNING步骤视为未完成 - 统一重置为
FAILED - 续跑时从第一个非
SUCCESS步骤开始
这样可以保留“上次停在这里”的事实,并避免把 RUNNING 误判为仍在健康执行。
Status Semantics
Task status
PENDING: 任务已创建,尚未开始RUNNING: 已受理且后台执行中SUCCESS: 全部步骤成功FAILED: 某一步失败并已停止
Step status
PENDING: 从未执行RUNNING: 当前正在执行SUCCESS: 已成功,后续重试跳过FAILED: 最近一次执行失败,后续重试从此处继续
Concurrency Policy
Same task
同一任务重复点击执行:
- 若本实例检测到该任务已在执行,直接返回当前状态
- 不再启动第二个后台线程
Different tasks
不同任务是否允许并发执行,第一版保持当前默认:
- 允许不同
taskId并行受理 - 不额外加全局串行锁
原因:
- 当前需求只明确要求单任务避免重入
- 是否需要全局串行,取决于目标主机资源和脚本互斥关系,后续再单独设计
Data Changes
本设计优先复用现有表结构,不新增表字段。
依赖现有字段即可表达:
- 任务当前状态
- 步骤当前状态
- 失败消息
- 日志路径
- 退出码
Testing Strategy
需要补的测试重点:
- 异步受理
execute调用应快速返回- 后台线程继续推进步骤状态
- 失败后续跑
- 首次执行在某步失败
- 再次执行时跳过已
SUCCESS步骤 - 从失败步骤重新执行并继续后续步骤
- 残留
RUNNING
- 构造上一次异常中断留下的
RUNNING步骤 - 重试时应从该步骤继续
- 单任务重复触发保护
- 同一个
taskId在执行中再次调用 - 不应重复启动第二个执行线程
Risks
- 应用内异步执行依赖当前进程存活,若服务重启,运行中的任务不会自动恢复。
- 若底层初始化脚本本身不具备完全幂等性,虽然步骤级跳过已成功步骤可以减少重复执行,但失败步骤重试仍可能触发环境冲突。
- 当前只做单实例内去重;若后续部署多实例,需要补分布式锁或数据库抢占机制。
Recommendation
先实现最小闭环:
execute异步受理- 单任务进程内防重入
- 从第一个非
SUCCESS步骤续跑 - 残留
RUNNING视为失败点恢复
不在第一版加入:
- 分布式调度
- 自动恢复未完成任务
- 新状态枚举或额外任务队列表