各位看官,上一篇《Spark源码分析之Stage划分》详细讲述了Spark中Stage的划分,下面,我们进入第三个阶段--Stage提交。
Stage提交阶段的主要目的就一个,就是将每个Stage生成一组Task,即TaskSet,其处理流程如下图所示:
与Stage划分阶段一样,我们还是从handleJobSubmitted()方法入手,在Stage划分阶段,包括最好的ResultStage和前面的若干ShuffleMapStage均已生成,那么顺理成章的下一步便是Stage的提交。在handleJobSubmitted()方法的最后两行代码,便是Stage提交的处理。代码如下:
// 提交最后一个stage
submitStage(finalStage)
// 提交其他正在等待的stage
submitWaitingStages()
从代码我们可以看出,Stage提交的逻辑顺序,是由后往前,即先提交最后一个finalStage,即ResultStage,然后再提交其parent stages,但是实际物理顺序是否如此呢?我们首先看下finalStage的提交,方法submitStage()代码如下:
/** Submits stage, but first recursively submits any missing parents. */
// 提交stage,但是首先要递归的提交所有的missing父stage
private def submitStage(stage: Stage) {
// 根据stage获取jobId
val jobId = activeJobForStage(stage)
if (jobId.isDefined) {// 如果jobId已定义
// 记录Debug日志信息:submitStage(stage)
logDebug("submitStage(" + stage + ")")
// 如果在waitingStages、runningStages或
// failedStages任意一个中,不予处理
// 既不在waitingStages中,也不在runningStages中,还不在failedStages中
// 说明未处理过
if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
// 调用getMissingParentStages()方法,获取stage还没有提交的parent
val missing = getMissingParentStages(stage).sortBy(_.id)
logDebug("missing: " + missing)
if (missing.isEmpty) {
// 如果missing为空,说明是没有parent的stage或者其parent stages已提交,
// 则调用submitMissingTasks()方法,提交tasks
logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
submitMissingTasks(stage, jobId.get)
} else {
// 否则,说明其parent还没有提交,递归,循环missing,提交每个stage
for (parent <- missing) {
submitStage(parent)
}
// 将该stage加入到waitingStages中
waitingStages += stage
}
}
} else {
// 放弃该Stage
abortStage(stage, "No active job for stage " + stage.id, None)
}
}
代码逻辑比较简单。根据stage获取到jobId,如果jobId未定义,说明该stage不属于明确的Job,则调用abortStage()方法放弃该stage。如果jobId已定义的话,则需要判断该stage属于waitingStages、runningStages、failedStages中任意一个,则该stage忽略,不被处理。顾名思义,waitingStages为等待处理的stages,spark采取由后往前的顺序处理stage提交,即先处理child stage,然后再处理parent stage,所以位于waitingStages中的stage,由于其child stage尚未处理,所以必须等待,runningStages为正在运行的stages,正在运行意味着已经提交了,所以无需再提交,而最后的failedStages就是失败的stages,既然已经失败了,再提交也还是会失败,徒劳无益啊~
此时,如果stage不位于上述三个数据结构中,则可以继续执行提交流程。接下来该怎么做呢?
首先调用getMissingParentStages()方法,获取stage还没有提交的parent,即missing;如果missing为空,说明该stage要么没有parent stage,要么其parent stages都已被提交,此时该stage就可以被提交,用于提交的方法submitMissingTasks()我们稍后分析。
如果missing不为空,则说明该stage还存在尚未被提交的parent stages,那么,我们就需要遍历missing,循环提交每个stage,并将该stage添加到waitingStages中,等待其parent stages都被提交后再被提交。
我们先看下这个missing是如何获取的。进入getMissingParentStages()方法,代码如下:
private def getMissingParentStages(stage: Stage): List[Stage] = {
// 存储尚未提交的parent stages,用于最后结果的返回
val missing = new HashSet[Stage]
// 已被处理的RDD集合
val visited = new HashSet[RDD[_]]
// We are manually maintaining a stack here to prevent StackOverflowE