为什么需要任务编排
任务往往不是孤立运行的,而是由多个相互依赖的子任务组成的复杂流程。例如,软件构建需要先编译核心库再编译依赖模块,分布式计算需要先完成数据分片再进行聚合处理,甚至在操作系统中,某些服务必须在驱动加载或网络初始化之后才能启动。随着系统规模和复杂度的增加,这些任务间的依赖关系变得庞杂,如果缺乏统一的编排与调度机制,容易出现执行顺序混乱、资源冲突、依赖缺失、错误恢复困难等问题。
DAG(Directed Acyclic Graph,有向无环图)任务编排系统能够用图结构清晰地描述任务及其依赖关系,通过拓扑排序确保任务在正确的顺序下高效执行,并支持并行运行、自动重试、监控和日志追踪。这种机制不仅适用于数据处理和分布式计算,也广泛应用于编译系统、持续集成流水线、容器编排、自动化运维等场景,大幅提升了系统的稳定性、可维护性和执行效率。
什么是DAG
DAG即有向无环图,是Directed Acyclic Graph的缩写,它是一种由节点(Vertex)和有向边(Edge)组成的数据结构,且图中不存在任何环路。 在任务编排场景中,节点通常代表一个独立的任务或计算步骤,边则表示任务之间的依赖关系,即:一条边从节点A指向节点B,意味着任务A必须先于任务B执行。

图源:https://github.com/noneback/go-taskflow
DAG的无环特性确保了任务依赖不会出现循环死锁,从而能够通过拓扑排序获得一个可执行的顺序。同时,图结构又能够清晰表达任务间的并行与串行关系。这种结构不仅直观易理解,还天然适合调度算法优化,因而被广泛应用于工作流引擎、分布式计算框架、编译系统、持续集成流水线等需要复杂依赖管理的场景。
任务节点定义
在DAG任务编排系统中,任务节点拟通过YAML文件进行统一描述,每个节点包含任务名称(name)、运行所需的系统环境(environment,例如操作系统版本、依赖库、容器镜像等)、输入(inputs,用于声明该任务依赖的上游输出或外部数据源)以及输出(outputs,用于标识该任务执行后生成的数据或供下游任务使用的结果)。
输入字段明确任务的依赖关系和数据传递路径;输出字段定义任务执行的产出,以供DAG中的其他节点消费。通过这种YAML描述方式,任务编排器可以解析依赖关系、构建任务拓扑,并在运行时按拓扑顺序自动调度各任务节点的执行。
任务名称与系统环境
任务名称用于在整个DAG任务编排中唯一标识一个任务节点,例如taskA任务可以如下定义:
|
|
系统环境字段确保任务在一致且可复现的运行环境中执行,例如taskA任务可以如下定义操作系统、硬件规格(CPU、内存、GPU)、依赖软件版本等信息:
|
|
任务输入与输出
输入字段用于定义任务在执行前所需的数据、文件或参数。输入既可以是静态指定,也可以引用前置任务的输出,从而在DAG中建立清晰的数据依赖关系:
- training_data是一个类型为文件的输入,来源为静态配置,直接指定路径为data/train.csv,格式为CSV文件;
- learning_rate是一个类型为参数的静态输入,值为0.01;
- preprocessed_data是一个类型为数据集的输入,其来源为模块变量,通过taskB.outputs.processed_data引用DAG中另一个任务taskB的输出,实现任务间的数据传递。
|
|
输出字段配置定义了任务执行完成后生成的结果,既可以是简单的数值,也可以是文件,用于下游任务的引用或外部系统使用:
- trained_model是一个类型为value的输出,格式为整数;
- metrics_report是一个类型为file的输出,格式为JSON。
|
|
任务启动与清理
在 DAG 任务节点中,每个任务除了定义名称、环境、输入和输出外,还需要定义任务的执行行为。例如远程计算节点 192.168.100.100:86的脚本./run_task.sh负责任务的具体执行:
- 脚本首先读取YAML中声明的inputs数据,并根据任务依赖获取上游任务生成的输出;
- 在预先配置的系统环境中启动程序或脚本,执行实际的数据处理或计算逻辑;
- 收集程序生成的输出文件或数据流,对其进行必要的后处理(如格式转换、压缩、校验等),并将处理后的结果写入任务定义中的outputs,以供 DAG 中的下游任务消费。
|
|
依赖关系解析与DAG生成
在定义任务的输入输出之后,DAG调度系统可以进行依赖关系解析。系统会根据每个输入项中对其它模块的引用,分析任务与其他模块或任务之间的依赖关系,从而构建完整的DAG拓扑结构。这种依赖分析不仅确保上游任务的输出在当前任务执行前可用,还可以自动生成任务调度顺序,保证数据流在DAG中正确传递。
为了防止任务间的循环依赖或逻辑错误,系统需要支持死锁检测。通过检查任务之间的依赖图是否存在环(循环依赖),调度系统可以在任务执行前发现潜在的死锁风险,并向用户报警或拒绝执行,确保DAG的执行始终可行且安全。
在实现上,依赖关系解析通常包括以下步骤:
- 遍历所有任务的输入定义,收集对其他模块变量的引用;
- 构建有向图,其中节点表示任务,边表示依赖关系(上游任务输出 > 下游任务输入);
- 对有向图进行环路检测,如果发现循环依赖,则触发死锁警告或异常;
- 基于拓扑排序生成任务执行顺序,保证所有任务在其依赖完成后才开始执行。
工程实施
后续考虑添加条件判断、循环执行等功能,工程实施进行中……