一、RDD依赖
在Spark中,对RDD的每一次转化操作都会生成一个新的RDD,由于RDD的懒加载特性,新的RDD会依赖原有RDD,因此RDD之间存在类似流水线的前后依赖关系。这种依赖关系分为两种:窄依赖和宽依赖。
二、窄依赖
窄依赖是指父RDD的每一个分区最多被一个子RDD的分区使用,即OneToOneDependencies。窄依赖的表现一般分为两类,第一类表现为一个父RDD的分区对应于一个子RDD的分区;第二类表现为多个父RDD的分区对应于一个子RDD的分区。一个父RDD的一个分区不可能对应一个子RDD的多个分区。为了便于理解,我们通常把窄依赖形象地比喻为独生子女。
RDD做map、filter和union算子操作时,是属于窄依赖的第一类表现;而RDD做join算子操作(对输入进行协同划分)时,是属于窄依赖表现的第二类。输入协同划分是指多个父RDD的某一个分区的所有Key,被划分到子RDD的同一分区。当子RDD做算子操作,因为某个分区操作失败导致数据丢失时,只需要重新对父RDD中对应的分区做算子操作即可恢复数据。
(一)map()与filter()算子
一对一的依赖
(二)union()算子
一对一的依赖
(三)join()算子
多对一的依赖
对于窄依赖的RDD,根据父RDD的分区进行流水线操作,即可计算出子RDD的分区数据,整个操作可以在集群的一个节点上执行。
三、宽依赖
宽依赖是指子RDD的每一个分区都会使用所有父RDD的所有分区或多个分区,即OneToManyDependecies。为了便于理解,我们通常把宽依赖形象的比喻为超生。
父RDD做groupByKey和join(输入未协同划分)算子操作时,子RDD的每一个分区都会依赖于所有父RDD的所有分区。当子RDD做算子操作,因为某个分区操作失败导致数据丢失时,则需要重新对父RDD中的所有分区进行算子操作才能恢复数据。
例如,groupByKey()、reduceByKey()、sortByKey()等操作都会产生宽依赖。
(一)groupBy()算子
多对多的依赖
(二)join()算子
多对多的依赖
join()操作的依赖关系分两种情况:RDD的一个分区仅和另一个RDD中已知个数的分区进行组合,这种类型的join()操作是窄依赖,其他情况则是宽依赖。
在宽依赖关系中,RDD会根据每条记录的key进行不同分区的数据聚集,数据聚集的过程称作Shuffle,类似MapReduce中的Shuffle过程。举个生活中的例子,4个人一起打牌,打完牌后需要进行洗牌,这4个人相当于4个分区,每个人手里的牌则相当于分区里的数据,洗牌的过程可以理解为Shuffle。因此,Shuffle其实就是不同分区之间的数据聚集或者说数据混洗。Shuffle是一项耗费资源的操作,因为它涉及磁盘I/O、数据序列化和网络I/O。
(三)reduceByKey()算子
对一个RDD进行reduceByKey()操作,RDD中相同key的所有记录将进行聚合,而key相同的所有记录可能不在同一个分区中,甚至不在同一个节点上,但是该操作必须将这些记录聚集到一起进行计算才能保证结果准确,因此reduceByKey()操作会产生Shuffle,也会产生宽依赖。
三、两种依赖的比较
在数据容错方面,窄依赖要优于宽依赖。当子RDD的某一个分区的数据丢失时,若是窄依赖,则只需重算和该分区对应的父RDD分区即可,而宽依赖需要重算父RDD的所有分区。在groupByKey()操作中,若RDD2的分区1丢失,则需要重新计算RDD1的所有分区(分区1、分区2、分区3)才能对其进行恢复。此外,宽依赖在进行Shuffle之前,需要计算好所有父分区的数据,若某个父分区的数据未计算完毕,则需要等待。