Spark2.2 DAGScheduler源码分析[stage划分算法源码剖析]

概述

DAGScheduler的stage的划分算法:

  1. 会从触发action操作的那个rdd开始向前倒退;
  2. 首先会为最后一个rdd创建一个stage,之后向前倒推的时候,会判断rdd的依赖,如果发现rdd是宽依赖,就会将宽依赖的那个rdd创建一个新的stage,这个新的rdd就是新的stage的最后一个rdd;
  3. 继续倒推,依据rdd的宽窄依赖,进行stage的划分,直到遍历完所有的rdd。

这里写图片描述


源码分析

调度执行的入口

sc.runJob() 的调度执行的入口:dagScheduler.runJob()

  /**
   * 在RDD中在给定的分区上运行一个函数,并将结果传递给给定的处理程序函数。
   * 这是所有Spark actions 的主要入口点。
   * @param rdd 执行任务的目标RDD
   * @param func
   * @param partitions
   * @param resultHandler 回调每一个结果
   */
  def runJob[T, U: ClassTag](
      rdd: RDD[T],
      func: (TaskContext, Iterator[T]) => U,
      partitions: Seq[Int],
      resultHandler: (Int, U) => Unit): Unit = {
    if (stopped.get()) {
      throw new IllegalStateException("SparkContext has been shutdown")
    }
    val callSite = getCallSite
    val cleanedFunc = clean(func)
    logInfo("Starting job: " + callSite.shortForm)
    if (conf.getBoolean("spark.logLineage", false)) {
      logInfo("RDD's recursive dependencies:\n" + rdd.toDebugString)
    }
    /**
     * leen
     * 调度执行的入口:dagScheduler.runJob()
     */
    dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get)
    progressBar.foreach(_.finishAll())
    rdd.doCheckpoint()
  }

DAGScheduler 调度的核心入口

dagScheduler.runJob() —> dagScheduler.handleJobSubmitted()

  • 第一步:使用触发job的最后一个RDD,创建finalStage => ResultStage
  • 第二步:用finalStage创建一个job
  • 第三步: 将job加入内存缓存中,并启动job
  • 第四步:使用submitStage() 提交finalStage
  /**
   * leen
   * DAGScheduler 调度的核心入口
   *
   * stage划分算法的步骤:
   * 1.从finalStage倒退
   * 2.通过宽依赖,来进行Stage的划分
   * 3.使用递归,优先提交父Stage
   */
  private[scheduler] def handleJobSubmitted(jobId: Int,
                                            finalRDD: RDD[_],
                                            func: (TaskContext, Iterator[_]) => _,
                                            partitions: Array[Int],
                                            callSite: CallSite,
                                            listener: JobListener,
                                            properties: Properties) {

    var finalStage: ResultStage = null
    try {
      // 第一步:使用触发job的最后一个RDD,创建finalStage => ResultStage
      finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
    } catch {
      // 一个新的Stage的创建,可能会抛出一个异常,比如,当一个HadoopRDD运行的时候,它所依赖的HDFS上的文件被删除
      case e: Exception =>
        logWarning("Creating new stage failed due to exception - job: " + jobId, e)
        listener.jobFailed(e)
        return
    }

    // 第二步:用finalStage创建一个job
    val job = new ActiveJob(jobId, finalStage, callSite, listener, properties)
    clearCacheLocs()
    logInfo("Got job %s (%s) with %d output partitions".format(
      job.jobId, callSite.shortForm, partitions.length))
    logInfo("Final stage: " + finalStage + " (" + finalStage.name + ")")
    logInfo("Parents of final stage: " + finalStage.parents)
    logInfo("Missing parents: " + getMissingParentStages(finalStage))

    val jobSubmissionTime = clock.getTimeMillis()
    // 第三步: 将job加入内存缓存中
    jobIdToActiveJob(jobId) = job
    activeJobs += job
    finalStage.setActiveJob(job)
    val stageIds = jobIdToStageIds(jobId).toArray
    val stageInfos = stageIds.flatMap(id => stageIdToStage.get(id).map(_.latestInfo))
    listenerBus.post(
      SparkListenerJobStart(job.jobId, jobSubmissionTime, stageInfos, properties))
    //第四步:使用submitStage() 提交finalStage
    submitStage(finalStage)
  }

stage划分算法与task最佳位置判断的核心代码

submitStage(finalStage)

  /**
   * leen
   * 提交Stage,但是首先要递归提交每一个存在的父Stages
   */
  private def submitStage(stage: Stage) {
    val jobId = activeJobForStage(stage)
    if (jobId.isDefined) {
      logDebug("submitStage(" + stage + ")")
      if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {

        // 使用getMissingParentStages() 获得当前Stage的父Stage
        // 并按照ID,升序排序
        val missing = getMissingParentStages(stage).sortBy(_.id)

        logDebug("missing: " + missing)

        /**
         * 递归调用
         * 直到最初的Stage,它没有父Stage了
         * 那么此时就会被提交第一个stages ,stage0
         */
        if (missing.isEmpty) {
          logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
          submitMissingTasks(stage, jobId.get)
        } else {
          //递归调用submitStage(),去提交父Stage
          // 这里的递归,就是Stage划分算法的精髓
          for (parent <- missing) {
            submitStage(parent)
          }
          // 并且将当前的Stage,放入waittingStages等待Stage被调用的队列
          waitingStages += stage
        }
      }
    } else {
      abortStage(stage, "No active job for stage " + stage.id, None)
    }
  }

stage划分算法核心

getMissingParentStages()

  /**
   * 获取某个Stage的父Stages
   */
  private def getMissingParentStages(stage: Stage): List[Stage] = {
    val missing = new HashSet[Stage]
    val visited = new HashSet[RDD[_]]
    // 我们创建一个栈【先入后出】,避免由于循环遍历访问引起的栈溢出ERROR
    val waitingForVisit = new Stack[RDD[_]]

    def visit(rdd: RDD[_]) {
      // 如果不是访问过的RDD
      if (!visited(rdd)) {
        visited += rdd
        val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)
        if (rddHasUncachedPartitions) {
          // 遍历RDD的依赖
          /**
           * 所以说,其实对于每一种shuffle操作,不如reduceByKey,groupByKey,countByKey
           * 底层对应了三个RDD:MapPartitionsRDD, ShuffleRDD, MapPartitionsRDD
           */
          for (dep <- rdd.dependencies) {
            // 模式匹配
            dep match {
              /**
               * 如果是宽依赖
               * 那么使用宽依赖的那个RDD,创建一个 ShuffleMapStage
               * 默认最后一个Stage,不是ShuffleMapStage
               * 但是finalStage之前的所有Stage,都是ShuffleMapStage
               */
              case shufDep: ShuffleDependency[_, _, _] =>
                val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)
                if (!mapStage.isAvailable) {
                  //将父Stage,放入missing中;
                  missing += mapStage
                }

              /**
               * 如果是窄依赖,则将依赖的RDD放入栈中
               */
              case narrowDep: NarrowDependency[_] =>

                waitingForVisit.push(narrowDep.rdd)
            }
          }
        }
      }
    }
    // 首先,往栈中推入Stage的最后一个RDD
    waitingForVisit.push(stage.rdd)
    // 进行while循环,如果栈非空,则对Stage的最后一个RDD,调用自己的visit方法;
    while (waitingForVisit.nonEmpty) {
      // pop() 删除并返回栈中的最后一个元素【最后进去的元素】
      visit(waitingForVisit.pop())
    }

    missing.toList
  }

task启动的最佳位置

submitMissingTasks() 找出task启动的最佳位置

  • (1) 对于finalstage之外的stage创建ShuffleMapTask
  • (2) 对于finalstage创建ResultTask
  • (3) 在task创建之前,需要根据taskId,从finalRdd往前推,寻找到被cache或者checkPoint的位置,启动task;
  • (4) 最后,针对Stage的task,创建TaskSet对象,调用submitTasks()方法,提交taskSet
  /**
   * 当stage的父Stage被找出来,并且我们可以执行它的task的时候调用
   */
  private def submitMissingTasks(stage: Stage, jobId: Int) {
    logDebug("submitMissingTasks(" + stage + ")")

    // 首先计算出索引分区ids。
    val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
    val properties = jobIdToActiveJob(jobId).properties
    runningStages += stage
    stage match {
      case s: ShuffleMapStage =>
        outputCommitCoordinator.stageStart(stage = s.id, maxPartitionId = s.numPartitions - 1)
      case s: ResultStage =>
        outputCommitCoordinator.stageStart(
          stage = s.id, maxPartitionId = s.rdd.partitions.length - 1)
    }

    // 根据TaskId找到Task启动的最佳启动位置:PreferredLocs
    // 从finalRdd向前推,寻找被缓存的 / checkPoint的位置,启动task
    val taskIdToLocations: Map[Int, Seq[TaskLocation]] = try {
      stage match {
        case s: ShuffleMapStage =>
          partitionsToCompute.map { id => (id, getPreferredLocs(stage.rdd, id)) }.toMap
        case s: ResultStage =>
          partitionsToCompute.map { id =>
            val p = s.partitions(id)
            (id, getPreferredLocs(stage.rdd, p))
          }.toMap
      }
    } catch {
      case NonFatal(e) =>
        stage.makeNewStageAttempt(partitionsToCompute.size)
        listenerBus.post(SparkListenerStageSubmitted(stage.latestInfo, properties))
        abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}", Some(e))
        runningStages -= stage
        return
    }

    /**
     * leen
     * 为Stage创建指定数量的tasks
     */
    val tasks: Seq[Task[_]] = try {
      val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
      stage match {
        /**
         * 【1】对于finalStage之外的Stage,创建ShuffleMapTask
         */
        case stage: ShuffleMapStage =>
          stage.pendingPartitions.clear()
          partitionsToCompute.map { id =>
            //给每一个Partition创建一个task
            //给每一个task计算最佳位置
            val locs = taskIdToLocations(id)
            val part = stage.rdd.partitions(id)

            stage.pendingPartitions += id

            new ShuffleMapTask(stage.id, stage.latestInfo.attemptId,
              taskBinary, part, locs, properties, serializedTaskMetrics, Option(jobId),
              Option(sc.applicationId), sc.applicationAttemptId)
          }

        /**
         * 【2】 对于finalStage,创建ResultTask
         */
        case stage: ResultStage =>
          partitionsToCompute.map { id =>
            val p: Int = stage.partitions(id)
            val part = stage.rdd.partitions(p)
            val locs = taskIdToLocations(id)
            new ResultTask(stage.id, stage.latestInfo.attemptId,
              taskBinary, part, locs, id, properties, serializedTaskMetrics,
              Option(jobId), Option(sc.applicationId), sc.applicationAttemptId)
          }
      }
    } catch {
      case NonFatal(e) =>
        abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}", Some(e))
        runningStages -= stage
        return
    }

    if (tasks.size > 0) {
      logInfo(s"Submitting ${tasks.size} missing tasks from $stage (${stage.rdd}) (first 15 " +
        s"tasks are for partitions ${tasks.take(15).map(_.partitionId)})")

      /**
       * 【3】最后,针对Stage的task,创建TaskSet对象,
       *     调用submitTasks()方法,提交taskSet
       */
      // 默认情况下,我们的StandAlone模式使用的是TaskSchedulerImpl,TaskScheduler只是一个trait
      taskScheduler.submitTasks(new TaskSet(
        tasks.toArray, stage.id, stage.latestInfo.attemptId, jobId, properties))
      stage.latestInfo.submissionTime = Some(clock.getTimeMillis())
    } else {
      // 如果没有任务需要运行,我们应该在此标记完成的阶段
      markStageAsFinished(stage, None)

      val debugString = stage match {
        case stage: ShuffleMapStage =>
          s"Stage ${stage} is actually done; " +
            s"(available: ${stage.isAvailable}," +
            s"available outputs: ${stage.numAvailableOutputs}," +
            s"partitions: ${stage.numPartitions})"
        case stage: ResultStage =>
          s"Stage ${stage} is actually done; (partitions: ${stage.numPartitions})"
      }
      logDebug(debugString)

      submitWaitingChildStages(stage)
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生命不息丶折腾不止

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值