2、DAG调度模型:DAG在任务调度中的定义、节点与边的含义、DAG调度与普通定时调度的区别

2.1 DAG在任务调度中的定义

DAG(Directed Acyclic Graph,有向无环图) 是一种图数据结构,由一组顶点(Vertex)和一组有向边(Directed Edge)组成,且图中不存在任何环路。在任务调度领域,DAG 被用来建模一组具有依赖关系的作业,确保每个任务在其所有前置任务完成后才被触发执行。

DAG 调度模型的核心约束是:无环。如果任务 A 依赖任务 B,任务 B 又依赖任务 A,则构成循环依赖,调度器将无法确定执行顺序,导致死锁或调度失败。Quartz 在扩展 DAG 调度时,会在任务提交阶段进行环检测,拒绝包含环路的作业流。

关键定义: DAG 调度 = 有向边 + 无环约束 + 拓扑排序执行。

2.2 节点与边的含义

在 Quartz DAG 调度模型中,节点和边分别承载不同的语义:

元素 名称 含义 Quartz 实现示例
节点(Vertex) 任务节点 一个可执行的作业单元,包含作业类、触发器、参数配置 JobDetail + Trigger 封装为 DAGNode
边(Edge) 依赖关系 表示两个任务之间的执行顺序约束,方向从前置任务指向后置任务 addDependency(fromNode, toNode)
入度(In-degree) 前置依赖数 指向该节点的边的数量,入度为 0 表示无前置依赖,可立即执行 调度器通过入度计数器判断就绪任务
出度(Out-degree) 后继任务数 从该节点出发的边的数量,出度为 0 表示终端节点 终端节点完成后触发 DAG 完成回调

边的方向决定了数据流和控制流的传递路径。在 Quartz 扩展实现中,边还可以携带额外的元数据,例如:

  • 数据传递策略: 前置任务的输出结果如何传递给后置任务(内存传递、数据库传递、文件传递)。
  • 条件分支: 后置任务是否根据前置任务的执行结果(成功/失败/超时)决定是否触发。
  • 超时与重试: 边级别的超时阈值,若前置任务超时未完成,后置任务可跳过或进入等待队列。
DAG调度模型:节点与边结构 DAG调度模型:节点与边结构 节点 A 入度:0 | 出度:2 节点 B 入度:1 | 出度:1 节点 C 入度:1 | 出度:1 节点 D 入度:2 | 出度:0 💡 节点 A 入度为 0,可立即执行;节点 D 出度为 0,为终端节点。 边方向从前置指向后置,数据沿边传递。Quartz 调度器按拓扑序依次执行。

2.3 DAG调度与普通定时调度的区别

普通定时调度(如 Quartz 的 CronTrigger 或 SimpleTrigger)只关注任务在特定时间点或固定间隔的触发,任务之间相互独立,不存在依赖关系。DAG 调度则引入了任务间的执行顺序约束,调度器必须根据依赖关系动态决定任务的触发时机。

下表从多个维度对比两种调度模式:

对比维度 普通定时调度 DAG调度
触发方式 基于时间表达式(Cron/Simple) 基于依赖关系 + 拓扑排序
任务独立性 任务之间完全独立 任务之间存在强依赖关系
执行顺序 由触发时间决定,可能并行 由DAG拓扑序决定,前置完成后才触发后置
失败处理 单个任务失败不影响其他任务 前置任务失败可能导致下游任务阻塞或跳过
数据传递 无内置数据传递机制 边可携带数据传递策略(内存/DB/文件)
适用场景 日志清理、报表生成、心跳检测 ETL流水线、机器学习训练链、多阶段数据处理
Quartz原生支持 原生支持(CronTrigger/SimpleTrigger) 需扩展实现(Quartz DAG插件或自定义调度器)
实践建议: 如果任务之间没有数据依赖或顺序要求,优先使用普通定时调度,避免引入DAG的复杂性和额外开销。只有当任务链的上下游关系明确且必须按序执行时,才采用DAG调度模型。

2.4 DAG调度的执行流程

Quartz 扩展 DAG 调度时,典型的执行流程如下:

  1. 构建DAG: 定义所有任务节点,并通过 addDependency() 方法建立边关系。
  2. 环检测: 调度器对DAG进行拓扑排序,若检测到环则抛出异常并拒绝提交。
  3. 初始化入度表: 计算每个节点的入度,将入度为0的节点放入就绪队列。
  4. 并行执行就绪任务: 从就绪队列中取出任务,提交给Quartz线程池执行。
  5. 任务完成回调: 每个任务执行完成后,更新其后继节点的入度(减1)。
  6. 触发后继任务: 若后继节点入度变为0,则将其加入就绪队列。
  7. DAG完成: 当所有节点执行完毕(入度全部归零),触发DAG完成事件。
DAG调度 vs 普通定时调度对比 DAG调度 vs 普通定时调度对比 普通定时调度(独立任务) 任务1 (Cron) 任务2 (Cron) 任务3 (Cron) 时间轴:各任务按各自Cron独立触发,互不依赖 DAG调度(依赖链) 节点1 (起始) 节点2 节点3 节点4 (终端) 💡 左侧:三个任务独立运行,互不等待,各自按Cron触发。 右侧:节点1完成后才能触发节点2和节点3,两者都完成后才能触发节点4。执行顺序由DAG拓扑决定。

2.5 边界参数配置要点

在 Quartz DAG 调度扩展中,边界参数配置直接影响调度的稳定性和性能。以下是关键配置项:

参数名称 类型 默认值 说明
dag.maxConcurrency int 10 DAG中允许同时执行的最大任务数,防止资源耗尽
dag.nodeTimeout long (ms) 300000 单个节点的超时时间,超时后标记为失败并触发下游处理策略
dag.retryCount int 0 节点失败时的重试次数,仅对可重试任务生效
dag.retryInterval long (ms) 60000 重试间隔时间
dag.failStrategy enum SKIP_ALL 节点失败后的下游处理策略:SKIP_ALL(跳过所有下游)、SKIP_DIRECT(仅跳过直接下游)、WAIT(等待人工干预)
dag.dataPassMode enum MEMORY 数据传递模式:MEMORY(内存)、DATABASE(数据库)、FILE(文件)
避坑提示: 当 dag.maxConcurrency 设置过小时,即使有大量就绪任务也会被阻塞,导致DAG整体执行时间延长。建议根据Quartz线程池大小和任务类型动态调整该参数,通常设置为线程池大小的 1.5~2 倍。