第2部分 | Apache DolphinScheduler 的核心抽象模型
Source: Dev.to
本文是系列 深入探讨 Apache DolphinScheduler:从调度原理到实践中的 DataOps 的第 2 部分。
从源码和调度模型的角度出发,分析 DolphinScheduler 的核心抽象,聚焦 Workflow、TaskDefinition 与实例级对象之间的职责边界。
通过 DAG 示意图,说明调度器如何通过依赖评估驱动复杂编排。
前一篇文章:
A Scheduler Is More Than Just a “Timer”
为什么会有这么多对象?
在使用 DolphinScheduler 一段时间后,很多用户会问:
为什么系统会同时包含这么多对象——工作流定义、工作流实例、任务定义、任务实例?
这不是过度设计吗?
如果你查看源码以及调度器的实际运行方式,答案恰恰相反:
这些抽象被有意地分离,以控制复杂性。
工作流:永不“运行”的 DAG 蓝图
在 DolphinScheduler 的设计中,工作流(在源码中映射为 ProcessDefinition)从一开始就被定义为纯粹的静态结构。
它仅描述一小部分信息:
- 流程中存在哪些任务,
- 任务之间的依赖关系,
- 是否存在条件分支或子工作流。
这些共同构成了一个 DAG —— 但这个 DAG 永远不会自行执行。
从代码角度来看,工作流更像是一个结构化的配置对象,而不是调度实体。它在数据库中不记录执行结果(没有成功/失败状态、开始时间、结束时间),也不感知任何具体运行期间发生的情况。
设计原则: 结构与执行必须完全分离;否则,状态会污染定义。
Source: …
实际上 DAG 在 DolphinScheduler 中解决了什么
DAG 只有一个单一且明确的职责:
确定某个任务在给定时刻是否有资格被调度。
它 不关心 任务如何执行,也不关心执行结果的业务含义。它只评估依赖是否已经满足。
📌 此处可以使用 DAG 示意图来展示典型的多父依赖结构。
节点是否可以被调度并不取决于执行顺序,而是取决于 所有 上游依赖是否已完成。这是 DolphinScheduler 动态评估 DAG 的核心运行时逻辑。

在源码中,DAG 会在 process instance 启动时被解析为内存结构,该结构驱动后续所有的调度决策。
当 TaskInstance 的状态发生变化时,调度器并不是简单地 “向前推进”。相反,它会 重新评估 DAG,以确定哪些节点现在已经 解锁。这也是 DolphinScheduler 能自然支持:
- 并行执行,
- 条件分支,
- 失败阻塞。
这些能力并非硬编码的特性——它们是 DAG 推理的自然结果。
TaskDefinition: 任务的执行模板
如果说 Workflow 是一个流程的蓝图,那么 TaskDefinition 就是 单个任务的模板。
在源代码中,TaskDefinition 保存了关于 任务应如何执行 的所有信息,包括:
- 任务类型(Shell、SQL、Spark、Flink 等)
- 参数和脚本内容
- 失败策略、超时配置、资源设置
关键点: TaskDefinition 完全是无状态的。
你永远不会在 TaskDefinition 中看到诸如 “执行成功” 之类的字段,因为执行结果 不属于 定义。
public class TaskDefinition {
private Long id;
private String name;
private TaskType taskType;
private String taskParams;
private int timeout;
private int failRetryTimes;
// Note: no execution state here
}
TaskDefinition 唯一的职责是描述 如何运行,而不是 它是如何运行的。
流程定义 vs. 流程实例:真实的边界
要理解 DolphinScheduler,需要明确区分 定义 与 实例。
当工作流被触发时,系统会基于该工作流及其任务定义创建一整套运行时对象:
ProcessInstanceTaskInstance
ProcessInstance
表示 “此工作流的具体执行”。
public class ProcessInstance {
private Long id;
private Long processDefinitionId;
private ExecutionStatus state;
private Date startTime;
private Date endTime;
}
TaskInstance
表示 “此任务的具体执行”。
public class TaskInstance {
private Long id;
private Long taskDefinitionId;
private ExecutionStatus state;
private int retryTimes;
private Date startTime;
}
在 UI 中看到的所有状态——运行中、失败、重试、日志——完全存在于 实例层面,而不是定义层面。
定义是可复用的;实例是短暂的。
此区分是调度系统长期稳定性的基础。
如何通过这些抽象实现复杂编排
随着任务数量的增长、工作流的嵌套以及故障的常态化,缺乏明确抽象边界的系统很快就会失控。
DolphinScheduler 的模型分离实现了若干关键能力:
- 同一个工作流可以并发运行多个实例,互不干扰。
- 重试仅影响
TaskInstance,永不污染定义。 - DAG 评估与任务执行完全解耦。
- 调度逻辑围绕状态转移,而非业务逻辑。
从这个角度看,DolphinScheduler 并不是“管理任务”——它在 管理状态与依赖的演进。
摘要
如果你把 DolphinScheduler 当作“更强大的 Cron”,这些模型可能显得过于复杂。
但从系统和源代码的角度来看,它们构成了一个高度严谨、深度工程化的设计。
在下一篇文章中,我们将继续沿用此模型并探讨:
调度器如何围绕状态转换进行操作——以及故障是如何被吸收而不是被放大的。
