4. 异步任务编排基础:任务依赖、DAG 模型与任务状态机
在分布式数据库迁移场景中,单个迁移操作往往需要拆解为多个子任务(数据抽取、转换、校验、索引重建等),这些子任务之间存在明确的执行顺序与依赖关系。异步任务编排的核心目标就是管理这些任务的执行流程,确保它们按照正确的拓扑顺序运行,同时能够处理失败、重试与并发控制。本节将系统讲解任务依赖关系、DAG(有向无环图)模型以及任务状态机这三个基础概念。
4.1 任务依赖关系
任务依赖定义了任务之间的执行约束。常见的依赖类型包括:
| 依赖类型 | 语义 | 迁移场景示例 |
|---|---|---|
| 顺序依赖 | 任务 B 必须在任务 A 完成后才能开始 | 先完成全量数据导出,再启动增量同步 |
| 并行依赖 | 任务 C 必须等待任务 A 和任务 B 都完成后才能开始 | 等待多个分片的数据校验任务全部完成,再执行数据修复 |
| 条件依赖 | 任务 B 的执行取决于任务 A 的结果状态 | 若全量导出成功则执行增量同步,若失败则执行重试或回滚 |
| 资源依赖 | 任务共享同一资源(如数据库连接池),需互斥执行 | 同一数据库实例上的多个索引重建任务不能同时执行 |
设计要点:在定义依赖关系时,应避免隐式依赖(如通过全局变量或时间窗口耦合)。显式声明依赖关系是后续实现幂等重试和故障恢复的基础。
4.2 DAG(有向无环图)模型
DAG 是描述任务编排最常用的数学模型。图中的每个节点代表一个任务,每条有向边代表依赖关系。DAG 的核心约束是无环——即不能出现 A 依赖 B、B 又依赖 A 的循环依赖,否则任务将永远无法开始。
下图展示了一个典型的数据库迁移任务 DAG:
4.3 任务状态机
每个任务实例在其生命周期中会经历一系列状态转换。标准的状态机模型包含四个核心状态:
| 状态 | 含义 | 可转换到的状态 | 幂等性要求 |
|---|---|---|---|
| Pending | 任务已创建,等待依赖条件满足 | Running | 多次查询 Pending 状态应返回相同结果 |
| Running | 任务正在执行中 | Success, Failed | 重复触发 Running 不应导致重复执行 |
| Success | 任务执行成功 | (终态) | 重复标记 Success 必须幂等 |
| Failed | 任务执行失败 | Pending(重试) | 失败记录需保留原始上下文用于重试 |
状态转换图如下:
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 和状态机,一个典型的任务编排引擎执行流程如下:
- 构建 DAG:根据迁移计划生成任务节点和依赖边,初始化所有任务状态为 Pending。
- 拓扑排序:计算 DAG 的拓扑序列,确定任务的执行顺序。
- 就绪任务检测:扫描所有 Pending 状态的任务,检查其所有前置依赖是否已进入 Success 状态。若满足,则将任务加入就绪队列。
- 任务调度:从就绪队列中取出任务,通过原子状态转换(Pending → Running)获取执行权,然后提交给执行器。
- 结果回写:任务执行完成后,根据执行结果将状态更新为 Success 或 Failed。
- 失败处理:若任务失败,根据重试策略决定是否重置为 Pending 重新调度,或终止整个 DAG 执行。
- 完成判定:当所有任务都进入终态(Success 或 Failed)时,DAG 执行完毕。
注意:在步骤 3 的就绪任务检测中,必须处理死锁检测。如果 DAG 中存在循环依赖(虽然理论上不应存在),或者所有剩余任务都处于 Failed 状态且不允许重试,则 DAG 进入死锁状态,需要人工介入或触发全局回滚。