1.RDD依赖关系与stage
RDD依赖关系分为以下两种:窄依赖和宽依赖。
2.1窄依赖与宽依赖
2.1.1窄依赖
窄依赖就是指父RDD的每个分区只被一个子RDD分区使用,子RDD分区通常只对应常数个父RDD分区。窄依赖分为以下两类:
- 1个子RDD的分区对应于1个父RDD的分区,比如map,filter,union等算子
- 1个子RDD的分区对应于N个父RDD的分区,比如co-partioned join
2.1.2宽依赖(Shuffle依赖)
宽依赖就是指父RDD的每个分区都有可能被多个子RDD分区使用,子RDD分区通常对应父RDD所有分区。宽依赖分为以下两类:
- 1个父RDD对应非全部多个子RDD分区,比如groupByKey,reduceByKey,sortByKey
- 1个父RDD对应所有子RDD分区,比如未经协同划分的join
2.1.3宽窄依赖的区别
1.计算方式
宽依赖往往对应着shuffle操作,需要在运行过程中将同一个父RDD的分区传入到不同的子RDD分区中,中间可能涉及到多个节点之间的数据传输;
而窄依赖的每个父RDD的分区只会传入到一个子RDD分区中,通常可以在一个节点内就可以完成了。
2.重算方式
当RDD分区丢失时(某个节点故障),spark会对数据进行重算。
对于窄依赖,由于父RDD的一个分区只对应一个子RDD分区,这样只需要重算和子RDD分区对应的父RDD分区即可,所以这个重算对数据的利用率是100%的;
对于宽依赖,重算的父RDD分区对应多个子RDD分区,这样实际上父RDD 中只有一部分的数据是被用于恢复这个丢失的子RDD分区的,另一部分对应子RDD的其它未丢失分区,这就造成了多余的计算;更一般的,宽依赖中子RDD分区通常来自多个父RDD分区,极端情况下,所有的父RDD分区都要进行重新计算。
如下图所示,b1分区丢失,则需要重新计算a1,a2和a3,这就产生了冗余计算(a1,a2,a3中对应b2的数据)。
2.2 stage划分
2.2.1 stage概念
Spark任务会根据RDD之间的依赖关系,形成一个DAG有向无环图,DAG会提交给DAGScheduler,DAGScheduler会把DAG划分相互依赖的多个stage,划分stage的依据就是RDD之间的宽窄依赖。遇到宽依赖就划分stage,每个stage包含一个或多个task任务。然后将这些task以taskSet的形式提交给TaskScheduler运行。 stage是由一组并行的task组成。
2.2.2 为什么要划分stage
(1) 窄依赖(narrow dependencies)
可以支持在同一个集群Executor上,以pipeline管道形式顺序执行多条命令,例如在执行了map后,紧接着执行filter。分区内的计算收敛,不需要依赖所有分区的数据,可以并行地在不同节点进行计算。所以它的失败恢复也更有效,因为它只需要重新计算丢失的parent partition即可
(2)宽依赖(shuffle dependencies)
则需要所有的父分区都是可用的,必须等RDD的parent partition数据全部ready之后才能开始计算,可能还需要调用类似MapReduce之类的操作进行跨节点传递。从失败恢复的角度看,shuffle dependencies 牵涉RDD各级的多个parent partition。
划分完stage之后,同一个stage里面只有窄依赖,没有宽依赖,可以实现流水线计算,stage中的每一个分区对应一个task,同一个stage中就有很多可以并行运行的task
2.2.3 stage分为两个阶段
由于shuffle依赖必须等RDD的父RDD分区数据全部可读之后才能开始计算,因此Spark的设计是让父RDD将结果写在本地,完全写完之后,通知后面的RDD。后面的RDD则首先去读之前RDD的本地数据作为输入,然后进行运算。
由于上述特性,讲shuffle依赖就必须分为两个阶段(stage)去做:
- 第1个阶段(stage)
需要把结果shuffle到本地,例如reduceByKey,首先要聚合某个key的所有记录,才能进行下一步的reduce计算,这个汇聚的过程就是shuffle。 - 第二个阶段(stage)
则读入数据进行处理。
为什么要写在本地?
后面的RDD多个分区都要去读这个信息,如果放到内存,假如出现数据丢失,后面所有的步骤全部不能进行,违背了之前所说的需要父RDD分区数据全部ready的原则。
2.2.4 如何划分stage
同一个stage里面的task是可以并发执行的,下一个stage要等前一个stage ready(和map reduce的reduce需要等map过程ready一脉相承)。
Spark 将任务以 shuffle 依赖(宽依赖)为边界打散,划分多个 Stage. 最后的结果阶段叫做 ResultStage, 其它阶段叫 ShuffleMapStage, 从后往前推导,依将计算。Spark 将任务以 shuffle 依赖(宽依赖)为边界打散,划分多个 Stage. 最后的结果阶段叫做 ResultStage, 其它阶段叫 ShuffleMapStage, 从后往前推导,依将计算。
如下图所示:从G往前推,BG依赖于B和F。
BG是窄依赖,不用划分stage。B依赖于A,且A和B是宽依赖。所以A到B需要划分一个stage
FG之间是宽依赖,所有F到G需要划分一个stage,F依赖于D和E,但都是窄依赖,不划分stage,D依赖于C,窄依赖,不需要划分stage。
参考
Spark宽依赖与窄依赖
Spark宽依赖和窄依赖深度剖析
Spark --【宽依赖和窄依赖】
2.shuflle
了解了宽窄依赖后我们再具体看一下宽依赖的shuflle。
在Spark或Hadoop MapReduce的分布式计算框架中,数据被按照key分成一块一块的分区,打散分布在集群中各个节点的物理存储或内存空间中,每个计算任务一次处理一个分区,但map端和reduce端的计算任务并非按照一种方式对相同的分区进行计算,例如,当需要对数据进行排序时,就需要将key相同的数据分布到同一个分区中,原分区的数据需要被打乱重组,这个按照一定的规则对数据重新分区的过程就是Shuffle(洗牌)。
下图是一个计算,根据宽窄依赖已经划分了stage。
将对应的 RDD 标注上去后:
对于这一过程,我们分析其中的 Shuffle:
这其中就涉及到 Shuffle 过程,前一个 Stage 的 ShuffleMapTask 进行 Shuffle Write, 把数据存储在 BlockManager 上面, 并且把数据位置元信息上报到 Driver 的 MapOutTrack 组件中, 下一个 Stage 根据数据位置元信息, 进行 Shuffle Read, 拉取上个 Stage 的输出数据。
触发Shuffle的操作
参考
Spark Shuffle的技术演进
Spark Shuffle 详解
https://juejin.im/post/6844904098047721486