DAGScheduler的原理剖析以及源码解析

4 篇文章 0 订阅
2 篇文章 0 订阅

原理讲解

DAGScheduler的stage划分算法:会从触发的action操作的那个rdd开始往前倒推,首先会为最后一个rdd创建一个stage,然后往前倒推的时候,如果发现对某个rdd是宽依赖,那么就会将宽依赖的那个rdd创建一个新的stage,那个rdd就是对新的stage的最后一个rdd,然后依次类推,继续往前倒推,根据宽窄依赖,进行stage的划分,直到所有的rdd全部遍历完了为之。

源码讲解

在代码执行了算子之后,比如count(),代码依次如下

def count(): Long = sc.runJob(this, Utils.getIteratorSize _).sum
  def runJob[T, U: ClassTag](rdd: RDD[T], func: Iterator[T] => U): Array[U] = {
    runJob(rdd, func, 0 until rdd.partitions.length)
  }
  def runJob[T, U: ClassTag](
      rdd: RDD[T],
      func: Iterator[T] => U,
      partitions: Seq[Int]): Array[U] = {
    val cleanedFunc = clean(func)
    runJob(rdd, (ctx: TaskContext, it: Iterator[T]) => cleanedFunc(it), partitions)
  }
  def runJob[T, U: ClassTag](
      rdd: RDD[T],
      func: (TaskContext, Iterator[T]) => U,
      partitions: Seq[Int]): Array[U] = {
    val results = new Array[U](partitions.size)
    runJob[T, U](rdd, func, partitions, (index, res) => results(index) = res)
    results
  }
  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)
    }

    // 调用SparkContext,之前初始化创建的DAGScheduler的runJob()方法
    dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get)
    progressBar.foreach(_.finishAll())
    rdd.doCheckpoint()
  }

经过一系列的runJob调用,最后走到了具体功能实现的函数,
这个函数中最重要的就是dagScheduler.runJob()方法,接着进入DAGScheduler的runJob函数,然后会调用submitJob() 函数,进入submitJob函数,DAGSchedulerEventProcessLoop 会post JobSubmitted的消息。

 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)

最终会调用DAGScheduler的handleJobSubmitted函数,这个函数是DAGScheduler的job调度的核心入口,接下来说明一下这个函数具体的实现步骤:

    var finalStage: ResultStage = null
    try {
      // New stage creation may throw an exception if, for example, jobs are run on a
      // HadoopRDD whose underlying HDFS files have been deleted.

      finalStage = newResultStage(finalRDD, func, partitions, jobId, callSite)
    } catch {
      case e: Exception =>
        logWarning("Creating new stage failed due to exception - job: " + jobId, e)
        listener.jobFailed(e)
        return
    }

第一步:使用触发job的最后一个rdd,创建finalStage , 并且将stage渐入DAGScheduler内部的内存缓存区

finalStage
    val job = new ActiveJob(jobId, finalStage, callSite, listener, properties)

第二步,用finalStage创建一个job, 就是说,这个job的最后一个stage,当然就是我们的finalStage

    jobIdToActiveJob(jobId) = job

第三部,将job加入内存缓存中

submitStage(finalStage)

第四部,使用submitStage方法提交finalStage,这个方法的调用,其实会导致第一个stage提交,并且导致其他所有的stage,都给放入waitingStage队列里。接下来我们看一下submitStage函数

  // 其实就是stage划分算法的入口
  // 但是,stage的划分,其实就是由submitStage方法与getMissingParentStages方法共同组成的
  private def submitStage(stage: Stage) {
    val jobId = activeJobForStage(stage)
    if (jobId.isDefined) {
      logDebug("submitStage(" + stage + ")")
      if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
        //调用getMissingParentStages方法,获取
        val missing = getMissingParentStages(stage).sortBy(_.id)

        logDebug("missing: " + missing)
        // 这里其实会反复调用
        // 直到最初的stage,它没有父stage了
        // 那么,此时,就会去首提交这个第一个stage
        // 其余的stage,此时全部都在waitingstage里面
        if (missing.isEmpty) {
          logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
          submitMissingTasks(stage, jobId.get)
        } else {
          // 递归调用submit方法,去提交父stage
          // 这里的递归,就是stage划分算法的推动者和精髓
          for (parent <- missing) {
            submitStage(parent)
          }
          // 并且将当前stage,放入waitingStage等待执行的stage的队列中
          waitingStages += stage
        }
      }
    } else {
      abortStage(stage, "No active job for stage " + stage.id, None)
    }
  }

其中比较重要的函数是getMissingParentStages,进入函数内部

// 获取某个stage的父stage
  // 对一个stage,如果它的最后一个rdd的所有依赖都是窄依赖,那么就不会创建任何新的stage
  // 但是,只要发现这个stage的rdd宽依赖了某个rdd,那么就用宽依赖的那个rdd,创建一个新的stage
  // 然后立即将新的stage返回
  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 Stack[RDD[_]]
    def visit(rdd: RDD[_]) {
      if (!visited(rdd)) {
        visited += rdd
        val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)

        if (rddHasUncachedPartitions) {
          // 遍历rdd的依赖
          // 其实对于每一种有shuffle的操作,比如groupByKey、reduceByKey、countByKey
          // 底层对应于三个RDD: Map.PartitionRDD(会归入的新的stage),ShuffleRDD,MapPartitionRDD
          //

          for (dep <- rdd.dependencies) {
            dep match {
                // 如果是宽依赖的话,
              case shufDep: ShuffleDependency[_, _, _] =>
                // 那么使用宽依赖的那个rdd,创建一个stage,并且会将isShuffleMap设置为true
                // 默认最后一个stage,不是shuffle stage
                // 但是finalStage之前的所有stage,都是shuffle stage
                val mapStage = getShuffleMapStage(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
  }

stage划分算法很重要,因为对于spark高手,或者spark精通人员来说 , 必须对stage划分算法很清晰,直到你自己编写的spark application被划分为几个job , 每个job划分成了几个stage
每个stage包括哪些代码 , 只有知道了每个stage包括了你的哪些代码之后 . 在线上,如果你发现某个stage执行特别慢,或者某个stage一致报错,你才能针对那个stage对应的代码去排查问题,或者性能调优

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值