- Spark里有很多RDD,且RDD之间有依赖关系。就像是一个单链表。
DAGScheduler
这个类的源码注释非常值得一看。- 我理解的计算分为不相关计算(filter,map,flatmap...)和相关计算(combineByKey,reduceByKey,groupByKey...) 其实也就是map和reduce。
- 不相关计算:可以单条数据进行计算。
- 相关计算:需要拿到一组数据进行计算,即拿到相同的key,这就需要有shuffle。
- stage分为两种:
ResultStage
ShuffleMapStage
stage由shuffle进行切分

- 当执行一个action算子的时候,会调起SparkContext#runJob方法。里面传入调起action算子的RDD。这个RDD也就是整个job的最后一个RDD,源码中叫
finalRDD
- 由于RDD有依赖关系,所以拿这finalRDD就能往前找到所有的RDD。
- 源码中用了一个递归
/** Submits stage, but first recursively submits any missing parents. */
private def submitStage(stage: Stage) {
// getMissingParentStages,这个代码里面用了一个栈,从finalRDD开始往前找
// missing是前一个stage的finalRDD,由于是递归,开始递归前一个stage
val missing = getMissingParentStages(stage).sortBy(_.id)
// 当最左的stage的时候,由于开始是HadoopRDD他是没有依赖的,所以递归触底
if (missing.isEmpty) {
submitMissingTasks(stage, jobId.get)
} else {
for (parent <- missing) {
submitStage(parent)
}
waitingStages += stage
}
}
}
private def getMissingParentStages(stage: Stage): List[Stage] = {
val missing = new HashSet[Stage]
val visited = new HashSet[RDD[_]]
// 这有个栈
val waitingForVisit = new ArrayStack[RDD[_]]
def visit(rdd: RDD[_]) {
if (!visited(rdd)) {
visited += rdd
val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)
if (rddHasUncachedPartitions) {
for (dep <- rdd.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)
if (!mapStage.isAvailable) {
// 遇到了shuffle
missing += mapStage
}
// 窄依赖 一直往前拿
case narrowDep: NarrowDependency[_] =>
waitingForVisit.push(narrowDep.rdd)
}
}
}
}
}
// stage.rdd就是一个stage中的finalRDD
waitingForVisit.push(stage.rdd)
// 栈不为空就一直拿 然后走上面的逻辑
while (waitingForVisit.nonEmpty) {
visit(waitingForVisit.pop())
}
missing.toList
}
和面试官扯
spark太吊了,让我们面向rdd编程,就像是本地代码线性执行。在我们写的代码中都是创建rdd的过程,并没有发生计算,而且rdd不存数据,是对数据的抽象。我们只需要编写逻辑,最后调用action算子,sparkcontext就会接管剩下的工作。由于rdd的有依赖关系的,且存在shuffle依赖和narrow依赖。通过栈这种数据结构就能往前得到shuffle依赖。并切分stage。这里还有递归的细节,带张纸和笔,如果他问,就给他画出来。这样能拖时间,并且加分。前提是聊明白。