RDD中的依赖关系

150 阅读6分钟

在RDD中通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种,一种是窄依赖(narrowdependency),另一种是宽依赖(widedependency),下游RDD的每个分区与上游 RDD (也称之为父RDD)的每个分区都有关,是多对多的关系。

图片.png

图片.png

窄依赖和宽依赖对比:

窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用 总结:窄依赖我们形象的比喻为独生子女,窄依赖的函数有:map, filter, union, join(父RDD是hash-partitioned ), mapPartitions, mapValues

宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition 总结:窄依赖我们形象的比喻为多生,宽依赖的函数有:groupByKey、partitionBy、reduceByKey、sortByKey、join(父RDD不是hash-partitioned)

图片.png

图片.png

图片.png

图片.png

窄依赖和宽依赖总结:

在这里我们是从父RDD的partition被使用的个数来定义窄依赖和宽依赖,因此可以用一句话概括下:如果父RDD的一个Partition被子RDD的一个Partition所使用就是窄依赖,否则的话就是宽依赖。因为是确定的partition数量的依赖关系,所以RDD之间的依赖关系就是窄依赖;由此我们可以得出一个推论:即窄依赖不仅包含一对一的窄依赖,还包含一对固定个数的窄依赖。一句话总结:窄依赖: 并行化+容错

一对固定个数的窄依赖的理解:即子RDD的partition对父RDD依赖的Partition的数量不会随着RDD数据规模的改变而改变;换句话说,无论是有100T的数据量还是1P的数据量,在窄依赖中,子RDD所依赖的父RDD的partition的个数是确定的,而宽依赖是shuffle级别的,数据量越大,那么子RDD所依赖的父RDD的个数就越多,从而子RDD所依赖的父RDD的partition的个数也会变得越来越多。一句话总结:宽依赖: 进行阶段划分(shuffle后的阶段需要等待shuffle前的阶段计算完才能执行)

DAG和Stage

图片.png

DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的Stage,对于窄依赖,partition的转换处理在Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据。 对于窄依赖:由于分区的依赖关系是确定的,其转换操作可以在同一个线程执行,所以可以划分到同一个执行阶段; 对于宽依赖:由于Shuffle的存在,只能在父RDD(s)被Shuffle处理完成后,才能开始接下来的计算,因此遇到宽依赖就需要重新划分阶段。

什么是stage?

图片.png

图片.png

图片.png

图片.png

在spark中,会根据RDD之间的依赖关系将DAG图(有向无环图)划分为不同的阶段,对于窄依赖,由于partition依赖关系的确定性,partition的转换处理就可以在同一个线程里完成,窄依赖就被spark划分到同一个stage中,而对于宽依赖,只能等父RDD shufflfflffle处理完成后,下一个stage才能开始接下来的计算。

首先,窄依赖允许在一个集群节点上以流水线的方式(pipeline)对父分区数据进行计算,例如先执行map操作,然后执行fifilter操作。而宽依赖则需要计算好所有父分区的数据,然后再在节点之间进行Shufflfflffle,这与MapReduce类似。窄依赖能够更有效地进行数据恢复,因为只需重新对丢失分区的父分区进行计算,且不同节点之间可以并行计算;而对于宽依赖而言,如果数据丢失,则需要对所有父分区数据进行计算并再次Shufflfflffle。

因此spark划分stage的整体思路是:从后往前推,遇到宽依赖就断开,划分为一个stage;遇到窄依赖就将这个RDD加入该stage中。因此在上图中RDD C,RDD D,RDD E,RDD F被构建在一个stage中,RDD A被构建在一个单独的Stage中,而RDD B和RDD G又被构建在同一个stage中。在spark中,Task的类型分为2种:ShuffleMapTaskResultTask简单来说,DAG的最后一个阶段会为每个结果的partition生成一个ResultTask,即每个Stage里面的Task的数量是由该Stage中最后一个RDD的Partition的数量所决定的!而其余所有阶段都会生成ShuffleMapTask;

之所以称之为ShuffleMapTask是因为它需要将自己的计算结果通过shuffle到下一个stage中;也就是说上图中的stage1和stage2相当于MapReduce中的Mapper,而ResultTask所代表的stage3就相当于MapReduce中的reducer。在之前动手操作了一个WordCount程序,因此可知,Hadoop中MapReduce操作中的Mapper和Reducer在spark中的基本等量算子是map和reduceByKey;不过区别在于:Hadoop中的MapReduce天生就是排序的;而reduceByKey只是根据Key进行reduce,但spark除了这两个算子还有其他的算子;因此从这个意义上来说,Spark比Hadoop的计算算子更为丰富。

接下来简单介绍下JOB提交任务执行流程

图片.png

图片.png

1.Application:应用,就是程序员编写的Spark代码,如WordCount代码

2.Driver:驱动程序,就是用来执行main方法的JVM进程,里面会执行一些Drive端的代码,如创建SparkContext,设置应用名,设置日志级别...

3.SparkContext:Spark运行时的上下文环境,用来和ClusterManager进行通信的,并进行资源的申请、任务的分配和监控等

4.ClusterManager:集群管理器,对于Standalone模式,就是Master,对于Yarn模式就是ResourceManager/ApplicationMaster,在集群上做统一的资源管理的进程

5.Worker:工作节点,是拥有CPU/内存等资源的机器,是真正干活的节点

6.Executor:运行在Worker中的JVM进程!

7.RDD:弹性分布式数据集

8.DAG:有向无环图,就是根据Action形成的RDD的执行流程图---静态的图

9.Job:作业,按照DAG进行执行就形成了Job---按照图动态的执行

10.Stage:DAG中,根据shuffle依赖划分出来的一个个的执行阶段!

11.Task:一个分区上的一系列操作(pipline上的一系列流水线操作)就是一个Task,同一个Stage中的多个Task可以并行执行!(一个Task由一个线程执行),所以也可以这样说:Task(线程)是运行在Executor(进程)中的最小单位!

12.TaskSet:任务集,就是同一个Stage中的各个Task组成的集合!