cisd/docs/plans/2026-03-10-init-execute-async-resume-design.md
2026-03-11 14:23:11 +08:00

5.8 KiB
Raw Blame History

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<String, Future<?>> 或等价结构维护活动任务。

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 调用应快速返回
  • 后台线程继续推进步骤状态
  1. 失败后续跑
  • 首次执行在某步失败
  • 再次执行时跳过已 SUCCESS 步骤
  • 从失败步骤重新执行并继续后续步骤
  1. 残留 RUNNING
  • 构造上一次异常中断留下的 RUNNING 步骤
  • 重试时应从该步骤继续
  1. 单任务重复触发保护
  • 同一个 taskId 在执行中再次调用
  • 不应重复启动第二个执行线程

Risks

  1. 应用内异步执行依赖当前进程存活,若服务重启,运行中的任务不会自动恢复。
  2. 若底层初始化脚本本身不具备完全幂等性,虽然步骤级跳过已成功步骤可以减少重复执行,但失败步骤重试仍可能触发环境冲突。
  3. 当前只做单实例内去重;若后续部署多实例,需要补分布式锁或数据库抢占机制。

Recommendation

先实现最小闭环:

  • execute 异步受理
  • 单任务进程内防重入
  • 从第一个非 SUCCESS 步骤续跑
  • 残留 RUNNING 视为失败点恢复

不在第一版加入:

  • 分布式调度
  • 自动恢复未完成任务
  • 新状态枚举或额外任务队列表