DAG 基础理论:有向无环图的调度基石

聊任务调度,绕不开 DAG。说白了,DAG 就是一张图,图里的边有方向,而且整个图里没有环路。我刚开始接触分布式调度时,总觉得这概念太理论,后来被线上一个死循环任务坑过一次,才真正明白 DAG 为什么是调度的基石。

有向无环图的定义

DAG 的全称是 Directed Acyclic Graph。拆开来看:

  • 有向(Directed):每条边都有方向,从 A 指向 B,表示 A 完成后才能开始 B。
  • 无环(Acyclic):从任意节点出发,沿着边的方向走,永远回不到起点。
  • 图(Graph):由节点(Vertex)和边(Edge)组成的数据结构。

举个例子,你想想看,一个数据管道:采集 → 清洗 → 聚合 → 输出。这就是典型的 DAG。采集指向清洗,清洗指向聚合,聚合指向输出,没有回头路。

核心要点:DAG 保证了任务之间只有单向依赖,不会出现 A 等 B、B 等 A 的死锁情况。这是它能用于调度的根本原因。

DAG 在调度中的角色

DAG 在调度系统里扮演什么角色?我个人的理解是——依赖关系的骨架。

具体来说,DAG 帮我们解决了三个核心问题:

  1. 执行顺序:哪些任务必须先跑,哪些可以并行。
  2. 资源规划:根据 DAG 的层级,提前分配计算资源。
  3. 故障恢复:某个节点挂了,只需要重跑它的上游,下游不受影响。

我记得在做一个实时数仓项目时,业务方要求每天凌晨 3 点前必须产出报表。我们用 DAG 把 200 多个任务串起来,哪个环节慢了,一眼就能看出瓶颈在哪。没有 DAG,这种规模的调度根本没法管理。

个人经验:设计 DAG 时,尽量让每个节点的粒度适中。太细了,图会变得臃肿,调度开销大;太粗了,并行度不够,资源利用率低。我一般控制在每个节点执行 5-15 分钟为宜。

DAG 的拓扑排序原理

拓扑排序,就是把 DAG 里的所有节点排成一个线性序列,保证每个节点都在它的所有依赖节点之后出现。说白了,就是给任务排个队,谁先谁后,清清楚楚。

算法其实不复杂,我常用的方法是 Kahn 算法:

// 伪代码:Kahn 拓扑排序
function topologicalSort(graph):
    inDegree = 计算每个节点的入度
    queue = 所有入度为 0 的节点
    
    while queue 不为空:
        node = queue.dequeue()
        result.append(node)
        
        for neighbor in graph[node]:
            inDegree[neighbor]--
            if inDegree[neighbor] == 0:
                queue.enqueue(neighbor)
    
    if result.length != graph.nodes.length:
        return "存在环,无法拓扑排序"
    return result

这个算法的核心思想很简单:先处理没有依赖的任务,然后逐步释放下游。每处理完一个节点,就把它的出边删掉,看有没有新的节点变成「无依赖」状态。

你可能会问,为什么入度为 0 就能执行?因为入度表示有多少个前置任务。入度为 0,说明所有前置都跑完了,当前任务可以放心启动。

避坑指南:我曾经在线上遇到过一个问题——拓扑排序结果不唯一。两个没有依赖关系的任务,谁先谁后?这会影响资源分配和最终结果吗?答案是:如果任务之间没有共享状态,顺序无所谓;如果有共享资源,需要额外加锁或依赖约束。

DAG 的合法性校验

合法性校验,说白了就是检查你画的 DAG 是不是真的「无环」。环是调度系统的头号杀手,一旦出现环,任务就会无限循环,资源被吃光,系统直接瘫痪。

校验方法主要有三种:

方法 原理 时间复杂度 适用场景
DFS 染色法 深度遍历,标记节点状态(未访问/访问中/已访问),遇到「访问中」即存在环 O(V+E) 小规模 DAG,需要精确定位环的路径
Kahn 算法 拓扑排序,如果最终排序节点数少于总节点数,说明有环 O(V+E) 大规模 DAG,只需要判断是否有环
并查集 合并节点集合,如果两个节点已经在同一集合中,说明有环 O(V·α(V)) 动态添加边时实时校验

我个人最常用的是 Kahn 算法,原因很简单:它不仅能检测环,还能顺便给出拓扑排序结果,一举两得。DFS 染色法虽然能定位环的具体路径,但实现起来稍微复杂一些。

关键点:合法性校验不能只在提交 DAG 时做一次。运行时也要做,因为某些调度系统支持动态添加任务。我见过一个案例,运维同学手动补数据时加了一条边,结果形成了环,整个集群卡了半小时。

下面这张图展示了 DAG 的核心概念和校验流程:

DAG 核心概念与校验流程 DAG 定义 有向 + 无环 + 图 调度角色 执行顺序 / 资源规划 / 故障恢复 拓扑排序 Kahn 算法 / DFS 合法性校验流程 1. 计算入度 2. 入度为 0 入队 3. 出队并减少下游入度 4. 节点数 < 总数? ✅ 无环,合法 DAG ❌ 有环,非法 DAG Kahn 算法 O(V+E)

工程建议:生产环境中,我建议把 DAG 校验做成一个独立的服务。每次提交 DAG 时,先调这个服务做合法性检查,通过后再写入存储。这样即使调度系统重启,校验逻辑也不会丢失。

嗯,DAG 的基础理论就这些。理解清楚定义、角色、拓扑排序和合法性校验,后面讲具体调度算法时,你会觉得顺滑很多。