概要
上一篇我们介绍了代表RDD组成的(Dependency、Partition、Partitioner)之一的Partition,这篇接着介绍Dependency。Partition记录的是数据split的逻辑,Dependency记录的是transformation操作过程中Partition的演化,即这个Partition从哪来到哪去的过程,以及通过Dependency的类型判断如何处理数据,即pipeline还是shuffle。
Dependency定义
我的另一篇博客Spark RDD中介绍了RDD的组成,及Dependency的分类以及缘由,这里不再累述,先看下Dependency的定义:
Dependency是抽象类,有一个属性rdd,就是对应RDD的父RDD,所以Dependency就是对父RDD的包装,并且通过Dependency的类型说明当前这个transformation对应的数据处理方式,其主要子类实现,即Dependency的类型有两大类:
- NarrowDependency(窄依赖)
窄依赖依然是抽象类,继承了rdd,并在定义中增加抽象方法getParents,根据子RDD的PartitionId返回对应的父RDD的PartitionId,接下来查看窄依赖的具体实现:
- OneToOneDependency
OneToOneDependency表示子RDD和父RDD的Partition之间的关系是1对1的,即子RDD的PartitionId和父RDD的PartitionId一样,如第一幅图中,Narrow Dependency下面的map和filter方法所示的关系。 - RangeDependency
RangeDependency表示子RDD和父RDD的Partition之间的关系是一个区间内的1对1对应关系,第一幅图中所示Narrow Dependency下面的union就是RangeDependency - PruneDependency
子RDD的Partition来自父RDD的多个Partition,filterByRange方法时会使用,不做详细讨论
- OneToOneDependency
- ShuffleDependency(宽依赖)
ShuffleDependency的定义相对复杂一些,因为shuffle设计到网络传输,所以要有序列化serializer,为了减少网络传输,可以加map端聚合,通过mapSideCombine和aggregator控制,还有key排序相关的keyOrdering,以及重输出的数据如何分区的partitioner,其他信息包括k,v和combiner的class信息以及shuffleId。shuffle是个相对复杂且开销大的过程,Partition之间的关系在shuffle处戛然而止,因此shuffle是划分stage的依据。
Dependency分为两大类,宽依赖和窄依赖,窄依赖有两个主要实现。
举个例子
以Wordcount为例
val wordcount = sc.parallelize(List("a c", "a b")) wordcount.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).collect()
通过web UI查看DAG,如下:
可以看出,其根据shuffle的位置划分为两个stage,stage0和stage1
调用toDebugString查看各RDD之间关系
最后,总结出Wordcount中RDD及其对应的Dependency如下,其中方形代表RDD,圆角矩形代表Partition(3个圆角矩形是为了作图方便,不代表其具体有3个Partition),文本框内第一行为代码片段,第二行是对应的RDD,第三行为RDD的Dependency类型
总结
Dependency是RDD的重要组成,分为宽依赖和窄依赖两大类,实质就是其父RDD的包装,由Dependency组成的关系构成了lineage的物理结构,也是DAG的物理结构,宽依赖(即shuffle操作)是stage划分的依据,窄依赖可以执行流水线(pipeline)操作,效率高。