Spark(六)-Stage划分算法

DAGScheduler提交job时,主要执行了:

val func2 = func.asInstanceOf[(TaskContext, Iterator[_]) => _]
val waiter = new JobWaiter(this, jobId, partitions.size, resultHandler)
eventProcessLoop.post(JobSubmitted(
    jobId, rdd, func2, partitions.toArray, callSite, waiter,
    SerializationUtils.clone(properties)))

eventProcessLoop的定义:

private[spark] val eventProcessLoop = new DAGSchedulerEventProcessLoop(this)

DAGSchedulerEventProcessLoop是class DAGScheduler的内部类,继承了EventLoop[DAGSchedulerEvent]。
**EventLoop**靠一个阻塞队列LinkedBlockingDeque保存事件队列,和一个eventThread线程来依次执行eventQueue的每一个事件。
线程函数名为onReceive(event)。

// EventLoop的post方法   
def post(event: E): Unit = {
    eventQueue.put(event)   
}

DAGScheduler提交job时,向eventProcessLoop Post了JobSubmitted事件,
DAGSchedulerEventProcessLoop的 onReceive() 收到触发事件时,匹配到JobSubmitted事件,执行 handleJobSubmitted()

  private def doOnReceive(event: DAGSchedulerEvent): Unit = event match {
    case JobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties) =>
      dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties)
    case ...
  }

在handleJobSubmitted()中,

  1. // 使用触发job的最后一个RDD来创建finalStage,new了一个 ResultStage
    try{ finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite) }
  2. // 用finalStage创建一个Job
    val job = new ActiveJob(jobId, finalStage, callSite, listener, properties)
  3. 把这个job保存到HashMap中,保存到activeJobs,添加引用到finalStage中
  4. 最后submitStage(finalStage)。这个方法会在递归后,反过来先提交第一个stage,
     然后执行到submitMissingTasks()最后的submitWaitingChildStages()时提交其他stage。

submitStage()提交stage, 先递归地提交所有missing parents的父stage.
为什么把missing parents的stage叫父stage呢?

一个Stage可能有多个RDD,RDD已经根据Action之前的Transformation算子建立了依赖关系。
submitStage()这里,spark要根据RDD的依赖关系,从最后一个stage开始往前追溯,把所有stage连接起来。
所谓的getMissingParentStages()其实是消除Missing的过程,也就是从最后一个孩子起,认祖归宗,连接出所有Parents。

所以,从finalStage起,获取到的missing stage都必是父Stage!

  /** 提交stage, 先递归地提交所有missing parents的父stage.  */
  private def submitStage(stage: Stage) {
    // 查找需要这个stage的最早创建的ActiveJob
    val jobId = activeJobForStage(stage)
    if (jobId.isDefined) {
      logDebug("submitStage(" + stage + ")")
      if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
        // 获取当前Stage尚未提交的所有父stages
        val missing = getMissingParentStages(stage).sortBy(_.id)
        logDebug("missing: " + missing)
        // 一直递归调用,直到最初的stage没有父stage了,就首先提交这个领头stage,
        // 此时,其余的stage全都被放到waitingStages队列了。
        if (missing.isEmpty) {
          logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
          submitMissingTasks(stage, jobId.get)
        } else {
          for (parent <- missing) {
            // 调用自己,递归地提交父stages
            submitStage(parent)
          }
          // 把当前stage加入到waitingStages等待队列中
          waitingStages += stage
        }
      }
    } else {
      abortStage(stage, "No active job for stage " + stage.id, None)
    }
  }

  // 如果一个stage的最后一个rdd的所有依赖都是窄依赖,就不会创建新的stage;
  // 只要这个stage的rdd依赖了一个宽依赖,就用那个宽依赖的rdd,创建一个新的stage(ShuffleMapStage)
  // 作为当前Stage的父stage,并且将所有的父stages返回。
  private def getMissingParentStages(stage: Stage): List[Stage] = {
    val missing = new HashSet[Stage]
    val visited = new HashSet[RDD[_]]
    // We are manually maintaining a stack here to prevent StackOverflowError
    // caused by recursively visiting
    val waitingForVisit = new ArrayStack[RDD[_]]
    def visit(rdd: RDD[_]) {
      if (!visited(rdd)) {
        visited += rdd
        val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)
        if (rddHasUncachedPartitions) {
          // 遍历RDD的依赖
          for (dep <- rdd.dependencies) {
            dep match {
              // 如果是宽依赖(ShuffleDependency),就对宽依赖的RDD创建一个ShuffleMapStage对象;
              // 然后把新建的shuffle map的stage放入到 missing parent的集合中去。
              // 默认宽依赖的最后一个stage不是ShuffleMapStage,但是finalStage之前的归入ShuffleMapStage,
              // 当前宽依赖对应的RDD,就作为这个宽依赖的最后一个RDD;
              case shufDep: ShuffleDependency[_, _, _] =>
                val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)
                if (!mapStage.isAvailable) {
                  missing += mapStage
                }
              // 如果是窄依赖,就把窄依赖的RDD入栈;
              case narrowDep: NarrowDependency[_] =>
                waitingForVisit.push(narrowDep.rdd)
            }
          }
        }
      }
    }
    // 首先往栈中推入最后一个stage的RDD
    waitingForVisit.push(stage.rdd)
    while (waitingForVisit.nonEmpty) {
      // 对最后一个stage的RDD,调用内部函数visit()
      visit(waitingForVisit.pop())
    }
    missing.toList
  }

getMissingParentStages()方法,首先往栈中推入最后一个stage的RDD,
然后visit()栈中pop出的每一个RDD:

  • 如果是窄依赖,就把窄依赖的RDD推入waitingForVisit栈,返回这里,继续visit!实现回溯。
  • 如果是宽依赖,就getOrCreate一个ShuffleMapStage,把当前stage的第一个RDD作为shuffle stage的最后一个,
          划分出新的Stage。

def createShuffleMapStage(shuffleDep: ShuffleDependency[_, _, _], jobId: Int): ShuffleMapStage = {
val rdd = shuffleDep.rdd
  …
}

猜你喜欢

转载自blog.csdn.net/rover2002/article/details/106093203