Spark切分stage

854 阅读2分钟
  • 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。这里还有递归的细节,带张纸和笔,如果他问,就给他画出来。这样能拖时间,并且加分。前提是聊明白。