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 扩展实现中,边还可以携带额外的元数据,例如:
- 数据传递策略: 前置任务的输出结果如何传递给后置任务(内存传递、数据库传递、文件传递)。
- 条件分支: 后置任务是否根据前置任务的执行结果(成功/失败/超时)决定是否触发。
- 超时与重试: 边级别的超时阈值,若前置任务超时未完成,后置任务可跳过或进入等待队列。
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 调度时,典型的执行流程如下:
- 构建DAG: 定义所有任务节点,并通过
addDependency()方法建立边关系。 - 环检测: 调度器对DAG进行拓扑排序,若检测到环则抛出异常并拒绝提交。
- 初始化入度表: 计算每个节点的入度,将入度为0的节点放入就绪队列。
- 并行执行就绪任务: 从就绪队列中取出任务,提交给Quartz线程池执行。
- 任务完成回调: 每个任务执行完成后,更新其后继节点的入度(减1)。
- 触发后继任务: 若后继节点入度变为0,则将其加入就绪队列。
- DAG完成: 当所有节点执行完毕(入度全部归零),触发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 倍。