4. 异步任务编排基础:任务依赖、DAG 模型与任务状态机

在分布式数据库迁移场景中,单个迁移操作往往需要拆解为多个子任务(数据抽取、转换、校验、索引重建等),这些子任务之间存在明确的执行顺序与依赖关系。异步任务编排的核心目标就是管理这些任务的执行流程,确保它们按照正确的拓扑顺序运行,同时能够处理失败、重试与并发控制。本节将系统讲解任务依赖关系、DAG(有向无环图)模型以及任务状态机这三个基础概念。

4.1 任务依赖关系

任务依赖定义了任务之间的执行约束。常见的依赖类型包括:

依赖类型 语义 迁移场景示例
顺序依赖 任务 B 必须在任务 A 完成后才能开始 先完成全量数据导出,再启动增量同步
并行依赖 任务 C 必须等待任务 A 和任务 B 都完成后才能开始 等待多个分片的数据校验任务全部完成,再执行数据修复
条件依赖 任务 B 的执行取决于任务 A 的结果状态 若全量导出成功则执行增量同步,若失败则执行重试或回滚
资源依赖 任务共享同一资源(如数据库连接池),需互斥执行 同一数据库实例上的多个索引重建任务不能同时执行
设计要点:在定义依赖关系时,应避免隐式依赖(如通过全局变量或时间窗口耦合)。显式声明依赖关系是后续实现幂等重试和故障恢复的基础。

4.2 DAG(有向无环图)模型

DAG 是描述任务编排最常用的数学模型。图中的每个节点代表一个任务,每条有向边代表依赖关系。DAG 的核心约束是无环——即不能出现 A 依赖 B、B 又依赖 A 的循环依赖,否则任务将永远无法开始。

下图展示了一个典型的数据库迁移任务 DAG:

数据库迁移任务 DAG 示例 数据库迁移任务 DAG 示例 全量数据导出 增量变更捕获 全量数据校验 目标索引重建 增量变更回放 数据修复与补偿 流量切换上线 说明:全量导出完成后,增量捕获、全量校验、索引重建可并行执行;所有前置任务完成后,才能执行流量切换上线。 数据修复任务仅在全量校验发现不一致时触发,属于条件依赖。

4.3 任务状态机

每个任务实例在其生命周期中会经历一系列状态转换。标准的状态机模型包含四个核心状态:

状态 含义 可转换到的状态 幂等性要求
Pending 任务已创建,等待依赖条件满足 Running 多次查询 Pending 状态应返回相同结果
Running 任务正在执行中 Success, Failed 重复触发 Running 不应导致重复执行
Success 任务执行成功 (终态) 重复标记 Success 必须幂等
Failed 任务执行失败 Pending(重试) 失败记录需保留原始上下文用于重试

状态转换图如下:

任务状态机转换图 任务状态机转换图 Pending Running Success Failed 依赖满足 执行成功 执行失败 重试(重置为 Pending) 说明:Failed 状态可通过重试机制回到 Pending 状态,重新参与调度。 Success 为终态,不允许再次转换;幂等性保证需确保重复标记 Success 不会产生副作用。

4.4 状态机在幂等性中的关键作用

任务状态机不仅是编排引擎的核心数据结构,更是实现幂等性的基础。在分布式系统中,任务可能被重复调度(如调度器重启、网络重试),状态机通过以下机制保证幂等:

  • 状态幂等检查:任务执行前先检查当前状态,只有 Pending 状态的任务才能被调度执行。如果任务已经是 Running 或 Success,则直接跳过,避免重复执行。
  • 状态转换原子性:状态转换操作(如 Pending → Running)必须使用乐观锁或分布式锁保证原子性,防止并发调度导致同一任务被执行多次。
  • 失败重试的幂等重置:将 Failed 任务重置为 Pending 时,必须同时清理或标记上一次执行的中间状态,确保重试时不会受到残留数据的影响。
实践建议:在实现任务状态机时,建议将状态持久化到数据库,并使用版本号(version)或 CAS(Compare-And-Swap)机制实现乐观锁。例如:
UPDATE task SET status = 'Running', version = version + 1
WHERE id = ? AND status = 'Pending' AND version = ?
这样即使调度器并发执行,也只有一个线程能成功将任务置为 Running 状态。

4.5 任务编排引擎的核心流程

基于 DAG 和状态机,一个典型的任务编排引擎执行流程如下:

  1. 构建 DAG:根据迁移计划生成任务节点和依赖边,初始化所有任务状态为 Pending。
  2. 拓扑排序:计算 DAG 的拓扑序列,确定任务的执行顺序。
  3. 就绪任务检测:扫描所有 Pending 状态的任务,检查其所有前置依赖是否已进入 Success 状态。若满足,则将任务加入就绪队列。
  4. 任务调度:从就绪队列中取出任务,通过原子状态转换(Pending → Running)获取执行权,然后提交给执行器。
  5. 结果回写:任务执行完成后,根据执行结果将状态更新为 Success 或 Failed。
  6. 失败处理:若任务失败,根据重试策略决定是否重置为 Pending 重新调度,或终止整个 DAG 执行。
  7. 完成判定:当所有任务都进入终态(Success 或 Failed)时,DAG 执行完毕。
注意:在步骤 3 的就绪任务检测中,必须处理死锁检测。如果 DAG 中存在循环依赖(虽然理论上不应存在),或者所有剩余任务都处于 Failed 状态且不允许重试,则 DAG 进入死锁状态,需要人工介入或触发全局回滚。