Spark任务计算源码剖析

任务计算源码剖析
理论指导

在这里插入图片描述
Spark在执行任务前期,会根据RDD的转换关系形成一个任务执行DAG。将任务划分成若干个stage。Spark底层在划分stage的依据是根据RDD间的依赖关系划分。Spark将RDD与RDD间的转换分类:ShuffleDependency-宽依赖NarrowDependency-窄依赖,Spark如果发现RDD与RDD之间存在窄依赖关系,系统会自动将存在窄依赖关系的RDD计算算子归纳为一个stage,如果遇到宽依赖系统开启一个新的stage。

Spark宽窄依赖判断

在这里插入图片描述

  • 宽依赖:父RDD的一个分区对应子RDD的多个分区,出现分叉就认定为宽依赖。
  • 窄依赖:父RDD的1个分区(多个父RDD)仅仅只对应子RDD的一个分区认定为窄依赖。

Spark在任务提交前期,首先根据finalRDD逆推出所有依赖RDD,以及RDD间依赖关系,如果遇到窄依赖合并在当前的stage中,如果是宽依赖开启新的stage。

在这里插入图片描述
getMissingParentStages
将宽窄依赖进行划分。这一步体现了之前所说的如果是窄依赖就合并在当前的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 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) {
                  missing += mapStage
                }
              case narrowDep: NarrowDependency[_] =>
                waitingForVisit.push(narrowDep.rdd)
            }
          }
        }
      }
    }
    waitingForVisit.push(stage.rdd)
    while (waitingForVisit.nonEmpty) {
      visit(waitingForVisit.pop())
    }
    missing.toList
  }

遇到宽依赖,系统会自动的创建一个ShuffleMapStage

submitMissingTasks
这一步是划分stage后,交由submitMissingTasks进行任务的分区以及映射TaskSet等。

private def submitMissingTasks(stage: Stage, jobId: Int) {
    logDebug("submitMissingTasks(" + stage + ")")

    // First figure out the indexes of partition ids to compute.
    //计算分区
    val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
 ...
 //计算最佳位置
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
    }
//将分区映射TaskSet
val tasks: Seq[Task[_]] = try {
      val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
      stage match {
        case stage: ShuffleMapStage =>
          stage.pendingPartitions.clear()
          partitionsToCompute.map { id =>
            val locs = taskIdToLocations(id)
            val part = partitions(id)
            stage.pendingPartitions += id
            new ShuffleMapTask(stage.id, stage.latestInfo.attemptNumber,
              taskBinary, part, locs, properties, serializedTaskMetrics, Option(jobId),
              Option(sc.applicationId), sc.applicationAttemptId, stage.rdd.isBarrier())
          }

        case stage: ResultStage =>
          partitionsToCompute.map { id =>
            val p: Int = stage.partitions(id)
            val part = partitions(p)
            val locs = taskIdToLocations(id)
            new ResultTask(stage.id, stage.latestInfo.attemptNumber,
              taskBinary, part, locs, id, properties, serializedTaskMetrics,
              Option(jobId), Option(sc.applicationId), sc.applicationAttemptId,
              stage.rdd.isBarrier())
          }
      }
    } catch {
      case NonFatal(e) =>
        abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}", Some(e))
        runningStages -= stage
        return
    }
//调用taskScheduler  #submitTasks  TaskSet
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)})")
      taskScheduler.submitTasks(new TaskSet(
        tasks.toArray, stage.id, stage.latestInfo.attemptNumber, jobId, properties))
    } else {
      // Because we posted SparkListenerStageSubmitted earlier, we should mark
      // the stage as completed here in case there are no tasks to run
      markStageAsFinished(stage, None)

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

...

总结关键字:逆推、finalRDD、ResultStage、ShuffleMapStage、ShuffleMapTask、ResultTask、ShuffleDependency、NarrowDependency、DAGScheduler、TaskScheduler、SchedulerBacked、DAGSchedulerEventProcessLoop

个人总结:SparkContext是由两个调度完成的,DAGSchedulerTaskScheduler,其中DAGScheduler负责stage的划分(相当于谋士一样,来计划谋略),而TaskScheduler是负责任务的资源计算、映射TaskSet、任务提交等。

TaskScheduler内部握有SchedulerBackend,是负责提交任务后,去与executor进程进行连接,以便执行任务。

stage会分为ResultStage(最终的stage)ShuffleMapStage,它们分别对应着Task
中的ResultTaskShuffleMapTask

DAGScheduler执行过程中有一个DAGSchedulerEventProcessLoop,它的内部实现是一个队列,是DAG调度程序的主事件循环。

在这之后根据finalRDD逆推出所有依赖RDD,以及RDD间依赖关系,如果遇到窄依赖(NarrowDependency)合并在当前的stage中,如果是宽依赖(ShuffleDependency)开启新的stage。

资料总结
job提交之后,调用runjob,到最终task被分配到executor之前所涉及到的调度相关

1.首先涉及到的调度是job stage 划分和提交过程,也就是submitStage方法,所有又依赖的Satge,也就是说有父Satge的子Stage,子Stage调用submitSatge的时候,会将子Satge添加到watingSatge队列中,换句话说,如果一个Stage有父依赖,那么他就不能被subnitMissingSatge submit,会被加入到watingSatge,只有没有依赖的Satge才会被提交。

没有依赖的Stage提交,会将Satge转换成tasksetManager,提交给TaskScheduar

2.taskSchedular在初始化的时候,方法位于sparkcontext中,初始化的时候初始化了一个队列,这个队列有两个选择:FIFO/FAIR,

tasksetManager提交给taskSchedular的时候就会加入到该队列中,比如FIFO队列,有两层排序,一层是根据jobid,jobid越小的优先级越高,同一job内部,存在第二层排序,stageid,stageid越小的优先级越高

值得注意的一点就是:stage提交的时候,有依赖,就不会添加到队列中,会加入到watingSatge中,等待某一个stage完成之后,会检查watingSatge提交已经没有依赖的Stage。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值