基本的划分流程请见https://www.jianshu.com/p/50c5c1032206,这里有一点异议。
上图:
在DAGSchedulerEventProcessLoop接收到JobSubmitted事件后,调用handleJobSubmitted方法,里面调用了newStage方法,这里第一次调用,传入newStage方法的是finalRDD
进入newStage方法,里面调用了getParentStages方法:
val parentStages: List[Stage] = getParentStages(rdd, jobId)
获取父stage列表,进入getParentStages方法:
private def getParentStages(rdd: RDD[_], jobId: Int): List[Stage] = {
val parents = new HashSet[Stage]
val visited = new HashSet[RDD[_]]
// We are manually maintaining a stack here to prevent StackOverflowError
// caused by recursively visiting
/*TODO 注意这个栈,里面保存RDD*/
val waitingForVisit = new Stack[RDD[_]]
/*TODO visit里面是栈弹出的finalRDD*/
def visit(r: RDD[_]) {
//finalRDD
//如果visited中没有r,执行if条件中的代码
if (!visited(r)) { //scala:这个r不在visited的HashSet中,返回false,即visited为空进入if
visited += r //将下面waitingForVisit弹出的这个r加入到visited中
// Kind of ugly: need to register RDDs with the cache here since
// we can't do it in its constructor because # of partitions is unknown
//TODO 获取这个r的依赖,因为父rdd可能是多个,所以依赖是多个--dependencies为复数
for (dep <- r.dependencies) { //当前rdd的上一级依赖,可能是多个依赖,遍历这些依赖
dep match {
/*TODO 判断依赖是宽依赖ShuffleDependency*/
case shufDep: ShuffleDependency[_, _, _] =>
/*TODO getShuffleMapStage方法和stage的划分有关
* TODO getShuffleMapStage是递归的方法,
* 1,有宽依赖,就执行getShuffleMapStage方法
* 2,是窄依赖,就进行压栈
* 3,返回的是stage,放入val parents = new HashSet[Stage]中
* */
/*TODO shufDep及其祖先的宽依赖的stage都放到shuffleToMapStage映射中
* */
parents += getShuffleMapStage(shufDep, jobId)
/*TODO 其他的是窄依赖*/
case _ =>
/*TODO 将这个依赖 dep即父rdd放入到waitingForVisit栈中,继续找窄依赖rdd的宽依赖*/
waitingForVisit.push(dep.rdd)
}
}
}
}
waitingForVisit.push(rdd)
/*TODO 当这个等待访问的栈不为空,就将其中的RDD弹出*/
while (!waitingForVisit.isEmpty) {
visit(waitingForVisit.pop()) //当获得finalRDD的上一级依赖对应的rdd被压入栈中再弹出,继续判断
}
/*Todo 返回当前rdd的所有的祖先的stage*/
parents.toList
}
进入getShuffleMapStage方法:
private def getShuffleMapStage(
shuffleDep: ShuffleDependency[_, _, _],
jobId: Int): Stage = {
shuffleToMapStage.get(shuffleDep.shuffleId) match {
case Some(stage) => stage
case None =>
// We are going to register ancestor shuffle dependencies
registerShuffleDependencies(shuffleDep, jobId)
// Then register current shuffleDep
val stage = newOrUsedStage(
shuffleDep.rdd, shuffleDep.rdd.partitions.size, shuffleDep, jobId,
shuffleDep.rdd.creationSite)
/*TODO 注意这个映射shuffleToMapStage,里面保存的是shuffleId->stage*/
shuffleToMapStage(shuffleDep.shuffleId) = stage
stage
}
}
进入registerShuffleDependencies方法:
private def registerShuffleDependencies(shuffleDep: ShuffleDependency[_, _, _], jobId: Int) = {
/*TODO 传进来的shuffleDep的rdd的所有宽依赖集合*/
val parentsWithNoMapStage: mutable.Stack[ShuffleDependency[_, _, _]] = getAncestorShuffleDependencies(shuffleDep.rdd)
while (!parentsWithNoMapStage.isEmpty) {
/*根据这个循环,pop的第一个是最远处即最左端的宽依赖,依据此宽依赖和该宽依赖的rdd调用newOrUsedStage方法*/
val currentShufDep = parentsWithNoMapStage.pop()
val stage =
newOrUsedStage(
currentShufDep.rdd, currentShufDep.rdd.partitions.size, currentShufDep, jobId,
currentShufDep.rdd.creationSite)
/*TODO 将所有宽依赖创建|获取的stage注册到shuffleToMapStage中*/
shuffleToMapStage(currentShufDep.shuffleId) = stage
}
}
进入主角:getAncestorShuffleDependencies方法
/*TODO 传进来的rdd是某个宽依赖的rdd*/
private def getAncestorShuffleDependencies(rdd: RDD[_]): Stack[ShuffleDependency[_, _, _]] = {
val parents = new Stack[ShuffleDependency[_, _, _]]
val visited = new HashSet[RDD[_]]
// We are manually maintaining a stack here to prevent StackOverflowError
// caused by recursively visiting
val waitingForVisit = new Stack[RDD[_]]
def visit(r: RDD[_]) {
if (!visited(r)) {
visited += r
for (dep <- r.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
if (!shuffleToMapStage.contains(shufDep.shuffleId)) {
/*TODO 如果shuffleToMapStage不包含该宽依赖的id,则该宽依赖放入parents中,
因为是新进来的需要被注册的stage,所以第一次找基于当前传进来的rdd的所有祖先宽依赖
TODO parents是栈,栈顶是最远处的即最左端的宽依赖
*/
parents.push(shufDep)
}
/*TODO 即便遇到宽依赖也将宽依赖的rdd压栈,意味着有传进来的rdd获得所有该rdd祖先的全部宽依赖*/
waitingForVisit.push(shufDep.rdd)
case _ =>
/*TODO 遇到窄依赖,将所依赖的rdd压栈*/
waitingForVisit.push(dep.rdd)
}
}
}
}
waitingForVisit.push(rdd)
while (!waitingForVisit.isEmpty) {
visit(waitingForVisit.pop())
}
parents //返回所有的宽依赖
}
getAncestorShuffleDependencies方法中:
1,注释标注了,是基于传递进来的rdd来查找这个rdd的所有祖先的宽依赖,不仅仅是父宽依赖,在循环调用visit方法过程中,parents栈顶存放的是与该rdd距离最远的宽依赖,比如上图中的stage1中rddA,也就是B的宽依赖
2,返回到调用getAncestorShuffleDependencies方法的registerShuffleDependencies中,返回当前rdd的所有祖先宽依赖后:
/*TODO 传进来的shuffleDep的rdd的所有宽依赖集合*/
val parentsWithNoMapStage: mutable.Stack[ShuffleDependency[_, _, _]] = getAncestorShuffleDependencies(shuffleDep.rdd)
上面的代码在对应方法中往下看,到这里:
val currentShufDep = parentsWithNoMapStage.pop()
parentsWithNoMapStage.pop()在一个循环中,不断的弹栈,弹出的即是从最左端的宽依赖,再往右弹:第二左宽依赖,第三左宽依赖。。。。这能理解吧。。。
3,紧接着会调用newOrUsedStage方法,该方法中有newStage方法,注意传递的参数,这里只考虑rdd的传递,如下:
/*TODO 这个方法的返回值被称作finalStage*/
private def newStage(
rdd: RDD[_], //这里传递进来的是finalRDD
numTasks: Int,
shuffleDep: Option[ShuffleDependency[_, _, _]],
jobId: Int,
callSite: CallSite)
: Stage = {
val parentStages: List[Stage] = getParentStages(rdd, jobId) //传递finalRDD
/*TODO nextStageId是AtomicInteger*/
val id = nextStageId.getAndIncrement()
//这里new了一个stage,注意传递的参数,rdd和parentStages,也就是这个new的stage会将rdd的父stage包起来
//同时注意,这里的rdd是宽依赖对应的rdd,也就是上图中B的宽依赖A或者G的宽依赖F
val stage = new Stage(id, rdd, numTasks, shuffleDep, parentStages, jobId, callSite)
stageIdToStage(id) = stage
updateJobIdStageIdMaps(jobId, stage)
/*TODO 这个返回值被称作finalStage*/
stage
}
-
说明:
- 1,在整个递归循环找stage或者宽依赖过程中,最先能返回stage的是最左端的宽依赖
- 2,返回祖先所有宽依赖后,循环中parents弹出的第一个是最左端的宽依赖,依据此宽依赖对应的rdd进入这个newStage方法
-
3,进入newStage方法后,依然会执行getParentStages方法,但是已经没有父或者祖先的可追溯的东西了,也就是parentStages为空,然后基于原子整数成员变量nextStageId获得一个id
val id = nextStageId.getAndIncrement()
,最左的这个stage为0
4,接下来val stage = new Stage(id, rdd, numTasks, shuffleDep, parentStages, jobId, callSite)
,这里将id,rdd,parentStages都传进来创建一个stage对象,即第一个stage:stage0
5,创建完stage0后就可以开始返回stage了,到上一层newOrUsedStage方法中,这里经过一系列操作,仍然将stage0返回给上一层registerShuffleDependencies方法中,得到stage0后将其注册到shuffleToMapStage中
shuffleToMapStage(currentShufDep.shuffleId) = stage
6,这里:注册后还在之前提到的弹出祖先宽依赖的循环中。这里开始弹第二左宽依赖了!!!弹出后依然进入newOrUsedStage方法中的newStage方法,这里传进来的是第二左宽依赖对应的rdd,这里说一下newStage方法中的getParentStages方法,该方法依然会找父stage,不同的是:a,stage0已经注册到了shuffleToMapStage中 b,getParentStages中找宽依赖还会调用getShuffleMapStage方法, 但该方法中会依据当前rdd的宽依赖找shuffleToMapStage中的stage, 第二宽依赖的父宽依赖对应的stage即stage0 c,所以,getParentStages返回的就是包含stage0的List[Stage]
7,接着看newStage执行完
val parentStages: List[Stage] = getParentStages(rdd, jobId)
后和stage0的方式一样,这里原子递增了一个id,即1,和当前第二左宽依赖对应的rdd和该rdd的父List[Stage]即包含stage0的parentStages,执行new stage对象,这里创建的就是stage1。对应上图中的就是stage3与stage1和stage2的关系
8,返回stage1后注册到shuffleToMapStage中
9,解释上图stage1和stage2谁先谁后,这个是基于parents这个宽窄依赖的栈中位置得到的,窄依赖多,那就靠栈顶一些,stage编号小一些;宽依赖多,就相对靠后,rdd的依赖是多个的,图中体现就是F->G以及A->B->G的关系