好的,我们来详细讲讲大数据领域中非常重要的一个概念——有向无环图(DAG)。
这是一个理解现代大数据计算框架(如Spark、Flink、Airflow等)核心工作原理的关键。
一、什么是DAG?(基础概念)
首先,我们拆解一下这个术语:
- 有向(Directed):表示任务之间的依赖关系是有方向的。比如任务A必须在任务B之前完成,这种关系是单向的。
- 无环(Acyclic):意味着这种依赖关系不能形成循环。换句话说,不存在这样一种情况:任务A依赖任务B,任务B又依赖任务C,而任务C反过来依赖任务A。这是一个死循环,永远无法开始执行。
- 图(Graph):由顶点(Vertex) 和边(Edge) 组成的数据结构。
在大数据语境下:
- 顶点 代表一个个具体的计算任务(Task) 或操作(Operation)(比如:
map,filter,join,reduce)。 - 边 代表任务之间的依赖关系 或数据流。一条从顶点A指向顶点B的边,通常意味着任务B依赖于任务A的输出。
所以,大数据中的DAG就是一个描述一系列计算任务及其依赖关系、且无循环依赖的执行计划图。
二、为什么DAG在大数据中如此重要?(优势)
在传统的MapReduce框架(如Hadoop MRv1)中,计算模型是线性的、僵化的:Map -> Shuffle -> Reduce。这种模型有两个主要缺点:
- 频繁落盘:每个阶段的结果都需要写入磁盘(HDFS),下一个阶段再从磁盘读取。大量的磁盘I/O操作非常耗时。
- 缺乏灵活性:复杂的计算逻辑需要拆分成多个MapReduce作业,并由开发者手动管理这些作业的依赖关系,非常繁琐。
而基于DAG的计算引擎(如Spark)完美地解决了这些问题:
-
提升性能(减少I/O):DAG调度器可以将多个操作“编织”成一个复杂的执行计划。在一个作业内部,只有需要物化(持久化) 或需要跨节点传输(Shuffle) 的中间结果才会被写入磁盘。其他多个连续的转换操作(如多个
map或filter)可以在内存中连续计算,极大减少了不必要的磁盘I/O,速度比MapReduce快了几个数量级。 -
表达复杂的计算逻辑:开发者可以用更高级的API(如Spark的RDD/DataFrame)描述复杂的多步计算,而无需关心如何将其分解成多个MapReduce作业。框架的DAG调度器会自动解析这些操作的依赖关系,构建出最优的执行图。
-
容错(Fault Tolerance):DAG记录了数据的整个变换 lineage(血统)。如果一个计算节点失败,导致某个分区的数据丢失,框架不需要从头开始重新计算整个作业。它可以根据DAG的血统信息,只重新计算该分区所依赖的父分区数据,大大提高了容错的效率。
-
优化执行(惰性求值与优化):像Spark这样的框架采用惰性执行(Lazy Evaluation)。当你定义一系列转换操作时,它们并不会立即执行,而是被记录在DAG中。这给了调度器一个全局视角,可以对整个执行计划进行优化,例如:
- 流水线执行(Pipelining):将多个窄依赖(Narrow Dependency)操作合并为一个阶段(Stage),在一个Task内连续执行。
- 谓词下推(Predicate Pushdown):在数据源读取时就进行过滤操作,减少后续处理的数据量。
三、一个具体的例子:Apache Spark中的DAG
我们以Spark处理一段代码为例,来直观感受DAG的形成和作用。
# 假设我们有一个Spark DataFrame的操作
lines = spark.read.text("hdfs://.../input.txt") # 1. 读取数据
words = lines.flatMap(lambda line: line.split(" ")) # 2. 切分单词
filtered_words = words.filter(lambda word: word.startswith("a")) # 3. 过滤出以'a'开头的单词
word_counts = filtered_words.groupBy("value").count() # 4. 分组计数
word_counts.write.csv("hdfs://.../output/") # 5. 写入结果
DAG的构建过程:
-
记录血统(Lineage):Spark不会立即执行上述操作,而是开始构建一个逻辑计划(Logical Plan) DAG,记录下每一步操作和依赖。
read->flatMap->filter->groupBy/count->write
-
优化:Spark的Catalyst优化器会对这个逻辑计划进行分析和优化(比如将
filter提前)。 -
生成物理计划(Physical Plan):根据逻辑DAG和数据的分布情况,生成具体的物理执行计划 DAG。这一步会明确如何执行,最重要的就是划分阶段(Stage)。
- 阶段的划分依据:Shuffle。Shuffle是一个需要所有节点网络通信、数据重新分区的昂贵操作,它也是阶段之间的边界。
- 在上面的例子中:
read,flatMap,filter都是窄转换(Narrow Transformation)(每个分区的计算不依赖其他分区的数据)。它们可以被合并到一个阶段(Stage 0) 中。groupBy().count()是一个宽转换(Wide Transformation)(需要Shuffle)。它必须开启一个新的阶段(Stage 1)。write是最终动作,属于最后一个阶段。
-
调度执行:DAG调度器将每个阶段分解为多个任务(Task)(每个分区一个任务),并将这些任务调度到集群的工作节点(Worker)上执行。任务调度器会确保Stage 0的所有任务都完成后,才会启动Stage 1的任务。
最终,这个作业的物理执行DAG可以可视化如下:
[Stage 0: (读取 + flatMap + filter)] --> (Shuffle) --> [Stage 1: (groupBy/count)] --> [Write]
四、不只是计算:DAG在工作流调度中的应用
DAG的概念不仅用于单个计算作业的内部,也广泛应用于工作流调度系统,如Apache Airflow、Luigi、Azkaban等。
在这些系统中:
- 顶点 代表的是整个作业(Job) 或任务(如一个Spark作业、一个SQL查询、一个Python脚本)。
- 边 代表的是作业间的依赖关系(如“作业A成功后再运行作业B”)。
这些调度器通过解析用户定义的DAG,来管理和监控复杂的数据管道(Data Pipeline)的定时、依赖和执行。例如,每天凌晨先运行数据抽取作业,成功后再依次运行数据清洗、转换、加载等作业。
总结
| 特性 | 在大数据中的体现 | 带来的好处 |
|---|---|---|
| 有向(Directed) | 清晰的任务依赖关系(B必须在A之后运行) | 保证计算逻辑的正确性 |
| 无环(Acyclic) | 任务间没有循环依赖 | 避免死锁,确保工作流总能开始和结束 |
| 图(Graph) | 将计算流程可视化 | 易于理解和调试复杂的计算管道 |
| (核心价值) | 作为执行计划 | 允许编译器进行全局优化(流水线、谓词下推) |
| (核心价值) | 记录数据血统(Lineage) | 实现高效容错(只重新计算丢失部分) |
总而言之,DAG是将大数据计算从“笨重”的批处理范式推向“高效灵活”的现代计算范式的核心抽象。它让框架能够智能地优化整个工作流,并高效地利用集群资源,是我们能够快速处理海量数据的基础。
2309

被折叠的 条评论
为什么被折叠?



