# 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`: 受理后的当前状态,通常为 `RUNNING` - `totalSteps`: 总步骤数 - `successSteps`: 当前已成功步骤数 前端后续继续通过以下接口轮询: - `GET /api/v1/init/tasks/{taskId}` - `GET /api/v1/init/tasks/{taskId}/steps` - `GET /api/v1/init/tasks/{taskId}/steps/{stepNo}/log` ## Execution Model ### 1. Request thread 请求线程只做以下事情: - 校验任务存在且有步骤 - 检查是否已经在执行中 - 将任务状态置为 `RUNNING` - 提交后台执行任务 - 立即返回 ### 2. Background worker 后台线程负责真正串行执行步骤: - 重新加载任务与步骤 - 识别续跑起点 - 逐步执行 - 每步结束后立刻落库状态、消息、日志路径、退出码 - 汇总任务最终状态 ### 3. Single-task guard 同一 `taskId` 在一个应用实例内只允许一个执行线程: - 若任务已在本实例执行中,再次调用 `execute` 不再重复启动 - 接口直接返回当前快照 实现上可使用进程内 `ConcurrentHashMap>` 或等价结构维护活动任务。 ## Resume Rule 再次执行时采用以下规则: 1. 所有 `SUCCESS` 步骤直接跳过,不重跑 2. 从第一个非 `SUCCESS` 步骤开始继续 3. 后续步骤按原顺序执行 4. 若本次再次失败,当前失败步骤状态记为 `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 需要补的测试重点: 1. 异步受理 - `execute` 调用应快速返回 - 后台线程继续推进步骤状态 2. 失败后续跑 - 首次执行在某步失败 - 再次执行时跳过已 `SUCCESS` 步骤 - 从失败步骤重新执行并继续后续步骤 3. 残留 `RUNNING` - 构造上一次异常中断留下的 `RUNNING` 步骤 - 重试时应从该步骤继续 4. 单任务重复触发保护 - 同一个 `taskId` 在执行中再次调用 - 不应重复启动第二个执行线程 ## Risks 1. 应用内异步执行依赖当前进程存活,若服务重启,运行中的任务不会自动恢复。 2. 若底层初始化脚本本身不具备完全幂等性,虽然步骤级跳过已成功步骤可以减少重复执行,但失败步骤重试仍可能触发环境冲突。 3. 当前只做单实例内去重;若后续部署多实例,需要补分布式锁或数据库抢占机制。 ## Recommendation 先实现最小闭环: - `execute` 异步受理 - 单任务进程内防重入 - 从第一个非 `SUCCESS` 步骤续跑 - 残留 `RUNNING` 视为失败点恢复 不在第一版加入: - 分布式调度 - 自动恢复未完成任务 - 新状态枚举或额外任务队列表