spark源码学习(四):Resultstage的产生和submitstage提交
上次我们对于stage的划分没有详细的划分,这里就来看看这些stage到底是通过什么流程来实现的。稍微的说一下,这次的程序进入的接口是从上次的Onreceive方法进入的。由里面的JobSubmitted模式来匹配。进入这个方法来分析具体的流程操作。首先进入JobSubmitted方法:
这里首先会创建一个newResultStage,作为finalStage,这里的finalStage代表的是上一blog的那个最后一个stage即为resultStage,resultStage的上一个就是shuffleMapStage,wordcount代码也就只有这两个Stage.这里我们明确在spark中的stage的创建是从后往前根据依赖的类型进行创建。newResultStage的代码创建如下:
上面代码中最重要的也就是getParentStageAndId,他主要是以当前的rdd也就是pairRddFunction和jobid作为参数,返回一个元组(parentStage,id),前面的代表这个当前的resultStage所依赖的全部stage,后面的就是返回当前stage的id,哈哈,具体就进入代码来看看吧:
感觉不是很明确吧,怎么还有一个方法呢?那就继续进入getParentStage来看看:
private def getParentStages(rdd: RDD[_], jobId: Int): List[Stage] = { val parents = new HashSet[Stage] val visited = new HashSet[RDD[_]] //用一个栈来维护依赖的RDD,这些RDD都是所对应的非shufflDependency val waitingForVisit = new Stack[RDD[_]] def visit(r: RDD[_]) { if (!visited(r)) { visited += r for (dep <- r.dependencies) { dep match {//这里的模式匹配是核心 case shufDep: ShuffleDependency[_, _, _] => parents += getShuffleMapStage(shufDep, jobId) case _ => waitingForVisit.push(dep.rdd) } } } } waitingForVisit.push(rdd) while (waitingForVisit.nonEmpty) { visit(waitingForVisit.pop()) } parents.toList }我们可以看到:如果是ShuffleDependency就添加到parents里面,如果是窄依赖的话就把当前的rdd压入栈底,然后就pop这个rdd,判断他的dependency是什么依赖。最后把parents转化成list。至于上面的nextStageId很简单,他就是一个整形数字而已:new AtomicInteger(0).简单吧。stageIdToStage(id) = stage就是把对应的id设置成为对应的stage,最后在向当前的job注册当前的stage,毕竟job是需要stage进行一系列的计算的嘛。
我们还要注意一下,在创建一个stage的时候,newResultStage里面的numTasks个数必须提前知道,因为要知道stage需要从多少个Partitions读取数据,直接影响创建多少个Task.
stage创建相关的代码结束,下面来看看和jActivejob相关的:
在源代码整体来看,jobId是体检就有的,但是真正看到newJob的地方确实是在这里。jobId的创建就是如下:
nextJobId = new AtomicInteger(0) jobId = nextJobId.getAndIncrement()。我也不明白为啥这样呢?看看activeJob:
然后下面的一些代码主要用于判断是否在本地执行,就不看了,看一下最重要的任务的提交submitStage函数
进入submitStage函数来看看吧:
(1)首先来看看getMissingParentStages这个函数,主要是找到没有提交的父stage,并且保存在missing中
(2) 然后在所有的stage都已经提交的情况下,调用submitMissingTasks才开始正儿八经的创建task,主要是根据依赖的类型来创建task,分为shuffleMapTask和resultTask两种任务。
这里的两个函数都是重中之重,明儿再继续分析,睡觉。。。