一、RDD的依赖关系
RDD的依赖关系分为两类:宽依赖和窄依赖。我们可以这样认为:
(1)窄依赖:每个parent RDD 的 partition 最多被 child RDD 的一个partition 使用。
(2)宽依赖:每个parent RDD partition 被多个 child RDD 的partition 使用。
窄依赖每个 child RDD 的 partition 的生成操作都是可以并行的,而宽依赖则需要所有的 parent RDD partition shuffle 结果得到后再进行。
二、org.apache.spark.Dependency.scala 源码解析
Dependency是一个抽象类:
1 | // Denpendency.scala |
它有两个子类:NarrowDependency 和 ShuffleDenpendency,分别对应窄依赖和宽依赖。
(1)NarrowDependency也是一个抽象类
定义了抽象方法getParents,输入partitionId,用于获得child RDD 的某个partition依赖的parent RDD的所有 partitions。
1 | // Denpendency.scala |
窄依赖又有两个具体的实现:OneToOneDependency和RangeDependency。
(a)OneToOneDependency指child RDD的partition只依赖于parent RDD 的一个partition,产生OneToOneDependency的算子有map,filter,flatMap等。可以看到getParents实现很简单,就是传进去一个partitionId,再把partitionId放在List里面传出去。
1 | // Denpendency.scala |
(2)ShuffleDependency指宽依赖
表示一个parent RDD的partition会被child RDD的partition使用多次。需要经过shuffle才能形成。
1 | // Denpendency.scala |
由于shuffle涉及到网络传输,所以要有序列化serializer,为了减少网络传输,可以map端聚合,通过mapSideCombine和aggregator控制,还有key排序相关的keyOrdering,以及重输出的数据如何分区的partitioner,还有一些class信息。Partition之间的关系在shuffle处戛然而止,因此shuffle是划分stage的依据。
三、两种依赖的区分
首先,窄依赖允许在一个集群节点上以流水线的方式(pipeline)计算所有父分区。例如,逐个元素地执行map、然后filter操作;而宽依赖则需要首先计算好所有父分区数据,然后在节点之间进行Shuffle,这与MapReduce类似。第二,窄依赖能够更有效地进行失效节点的恢复,即只需重新计算丢失RDD分区的父分区,而且不同节点之间可以并行计算;而对于一个宽依赖关系的Lineage图,单个节点失效可能导致这个RDD的所有祖先丢失部分分区,因而需要整体重新计算。