Spark2.3.2源码解析:9.调度系统 DAGScheduler 之 Stage 划分源码详解

Stage划分的时候,大家应该都知道是从最后一个stage向根据宽窄依赖,递归进行stage划分。

但是代码里面涉及的逻辑复杂。毕竟涉及到相互递归调用。让人似懂非懂。 反正我是炸毛了 o(╥﹏╥)o

本文专门用一篇文章详细论述DAGScheduler 的 stage 划分流程

为了更容易理解,本文采用 debug模式+实例+源码的方式进行讲解

首先写一个WordCount代码(这个代码,为了观察多个suffle操作,我写了两个reducebykey 函数)

源代码:

直接执行代码,查看spark执行程序时,将代码划分stage生成的DAG流程图

可知: WordCount 在stage划分的时候,划分为三个stage 

即在代码中如下标识:


 

首先,我们明确一个概念RDD

我们知道RDD有两个重要属性 id  , name

为了在后面调试的时候,清除的理解rdd之间的调用,需要对其做编号,本文以rdd的id进行区分

声明的rdd的属性我举几个例子:

zrdd1 : 

类型:  MapPartitionsRDD

id : 1

name : /tmp/zl/data/data.txt  (注:只有zrdd1的name有值,为数据路径, 其他的rddname值都是“null ”  )


zrdd4:

类型:  ShuffledRDD

id : 4

name : null

直接说结果:

属性 RDD Id  (重要,区分标识) RDD类型
zrdd1 1 MapPartitionsRDD
zrdd2 2 MapPartitionsRDD
zrdd3 3 MapPartitionsRDD
zrdd4 4 ShuffledRDD
zrdd5 5 MapPartitionsRDD
zrdd6 6 ShuffledRDD
zrdd7 7 MapPartitionsRDD

程序入口的触发点即为: zrdd7.count() 方法。  实际执行的是runjob方法。开启程序执行入口。

程序依赖关系如下图:

接下来我们看源码解析代码查看stage是如何划分的:

即如下代码调度流程图中标识的部分。

以为之前的文章有说明,所以不再详细解释。有兴趣的小伙伴可以直接看

https://blog.csdn.net/zhanglong_4444/article/details/85111604

好了,我们开始正式说代码:

org.apache.spark.scheduler.DAGScheduler#createResultStage

这个方法里面最重要的是getOrCreateParentStages 方法,从这就容易开始乱了。

别慌,我先给画个调用图,先搞清楚逻辑,再用debug跟一便就好了。

从图上可知,最外层循环的主体为: getOrCreateParentStages

记住这个啊。 这个才是真正的循环调用创建stage的方法,不要被getShuffleDependencies这个方法所迷惑

getShuffleDependencies 这个方法只是根据一个rdd返回这个rdd所在的宽依赖 ShuffleDependency

好了,先看一下类中的代码,然后我在画个图,讲解

getOrCreateParentStages: 

根据给定的RDD获取或者创建父stages列表 ,新的stage会根据提供的firstJobId进行创建 

这个方法很重要,递归调用的就是这个方法:

getShuffleDependencies

根据给定的RDD获取或者创建父stages列表 

 

返回值结构: ShuffleDependency 是一个宽依赖



getOrCreateShuffleMapStage  (这个方法注意看一下)

 

 

 

 

getMissingAncestorShuffleDependencies

这里面有一个递归方法 getShuffleDependencies 获取shuffle依赖 (缓存过的即为处理过的,不做任何处理)

ArrayStack  栈是一种后进先出(LIFO)的数据结构。 

所以在循环的时候,最先取出的值,是最后放进的值。

createShuffleMapStage

根据所给的 ShuffleDependency 创建 ShuffleMapStage

这个里面尤其要注意一点:

val parents = getOrCreateParentStages(rdd, jobId)

好了,接下来,我们画个图理解一下。

其实也不用画图。

主要是:

val deps =  getMissingAncestorShuffleDependencies(shuffleDep.rdd)

这句,直接会吧所有宽依赖的都会找出来,然后提交。

返回的数据结构是 ArrayStack 这个数据结构是栈是一种后进先出(LIFO)的数据结构

用递归的的方式拿到stage ,然后再取出

因为存储的时候,是栈存储,所以提交的时候是stage0, 带入上面的方法:

val parents = getOrCreateParentStages(rdd, jobId)

stage0没有parents,所以返回值,为空。然后将stage0加入缓存。 如下代码

stageIdToStage(id) = stage
shuffleIdToMapStage(shuffleDep.shuffleId) = stage

当在传入stage1的时候,获取父的依赖,也就是stage0,这个在上一次调用的时候,已经处理过了

已经获取到了,所以在调用getOrCreateParentStages方法的时候,可以直接从缓存中拿到值。

如下方法,直接从缓存中获取。相当于做了一个优化。

好了,下面是画图的方式说了一下,有不明白的地方可以给我留言。

举例:

根据代码划分:stage的时候是这个结构:

入栈:

出栈:

好了,接下来看一下图多个依赖,提交的时候。流程图

多个依赖提交例子 (深度遍历算法)

RDDs原始依赖图

getShuffleDependencies

RDD:15 , 获取上一层依赖,返回的结果是 ShuffleDependency 集合

getMissingAncestorShuffleDependencies

深度遍历顺序获取所有祖先的宽依赖,这里返回的是一个集合。 其实这个也是一个优化,如果采用递归方法的调用的话,

很容易因为嵌套层级过多,导致栈溢出。

传入值如果是RDD:13 返回 红色的宽依赖。

最后划分结果

就写到这里了,这部分有疑问或者有不对的地方

麻烦请指教,不胜感激。。。

参考链接:

http://spark.apache.org/docs/latest/

https://www.jianshu.com/p/14355e250e2f

猜你喜欢

转载自blog.csdn.net/zhanglong_4444/article/details/85201563