第2部分 | Apache DolphinScheduler 的核心抽象模型

发布: (2026年2月6日 GMT+8 11:10)
7 分钟阅读
原文: Dev.to

Source: Dev.to

本文是系列 深入探讨 Apache DolphinScheduler:从调度原理到实践中的 DataOps 的第 2 部分。

从源码和调度模型的角度出发,分析 DolphinScheduler 的核心抽象,聚焦 WorkflowTaskDefinition 与实例级对象之间的职责边界。

通过 DAG 示意图,说明调度器如何通过依赖评估驱动复杂编排。

前一篇文章:
A Scheduler Is More Than Just a “Timer”

为什么会有这么多对象?

在使用 DolphinScheduler 一段时间后,很多用户会问:

为什么系统会同时包含这么多对象——工作流定义、工作流实例、任务定义、任务实例?
这不是过度设计吗?

如果你查看源码以及调度器的实际运行方式,答案恰恰相反:

这些抽象被有意地分离,以控制复杂性。

工作流:永不“运行”的 DAG 蓝图

在 DolphinScheduler 的设计中,工作流(在源码中映射为 ProcessDefinition)从一开始就被定义为纯粹的静态结构
它仅描述一小部分信息:

  • 流程中存在哪些任务,
  • 任务之间的依赖关系,
  • 是否存在条件分支或子工作流。

这些共同构成了一个 DAG —— 但这个 DAG 永远不会自行执行

从代码角度来看,工作流更像是一个结构化的配置对象,而不是调度实体。它在数据库中不记录执行结果(没有成功/失败状态、开始时间、结束时间),也不感知任何具体运行期间发生的情况。

设计原则: 结构与执行必须完全分离;否则,状态会污染定义。

Source:

实际上 DAG 在 DolphinScheduler 中解决了什么

DAG 只有一个单一且明确的职责:

确定某个任务在给定时刻是否有资格被调度。

不关心 任务如何执行,也不关心执行结果的业务含义。它只评估依赖是否已经满足。

📌 此处可以使用 DAG 示意图来展示典型的多父依赖结构。

节点是否可以被调度并不取决于执行顺序,而是取决于 所有 上游依赖是否已完成。这是 DolphinScheduler 动态评估 DAG 的核心运行时逻辑。

DS 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,需要明确区分 定义实例

当工作流被触发时,系统会基于该工作流及其任务定义创建一整套运行时对象:

  • ProcessInstance
  • TaskInstance

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”,这些模型可能显得过于复杂。
但从系统和源代码的角度来看,它们构成了一个高度严谨、深度工程化的设计

在下一篇文章中,我们将继续沿用此模型并探讨:

调度器如何围绕状态转换进行操作——以及故障是如何被吸收而不是被放大的。

Back to Blog

相关文章

阅读更多 »