Spark应用程序-任务的划分

任务的划分

​ DAGScheduler类的handleJobSubmitted方法中,有一个提交阶段的的方法:

var finalStage: ResultStage = null
	……
finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
	……
submitStage(finalStage)

​ submitStage方法用于提交最终的ResultStage阶段,由于在最终的ResultStage可能包含了多个上级阶段,所以此处就相当于是提交整个应用程序的全部阶段。查看一下该方法的源码:

private def submitStage(stage: Stage): Unit = {
  val jobId = activeJobForStage(stage)
  if (jobId.isDefined) {
    logDebug(s"submitStage($stage (name=${stage.name};" +
      s"jobs=${stage.jobIds.toSeq.sorted.mkString(",")}))")
    if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
      val missing = getMissingParentStages(stage).sortBy(_.id)
      logDebug("missing: " + missing)
      if (missing.isEmpty) {
        logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
        submitMissingTasks(stage, jobId.get)
      } else {
        for (parent <- missing) {
          submitStage(parent)
        }
        waitingStages += stage
      }
    }
  } else {
    abortStage(stage, "No active job for stage " + stage.id, None)
  }
}

​ 该方法的内部核心逻辑是先获取当前阶段的的所有父级阶段,如果其父级阶段为空那么直接执行submitMissingTasks方法,如果不为空,那么递归执行submitStage方法,只不过传入的参数是当前阶段的父级阶段,一直递归直到找到没有上级阶段的阶段,最终没有上级阶段的那个阶段会执行submitMissingTasks方法。下面查看一下该方法的核心源码部分:

private def submitMissingTasks(stage: Stage, jobId: Int): Unit = {
    	 ……
  val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
		 ……
  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
  }

 	……
}

​ 核心代码的逻辑在于根据传入的stage进行模式匹配,会根据不同类型的Satge创建的不同的Task,那么首先会计算分区得到分区索引集合,然后使用map方法将根据分区id创建xxxMapTask对象,有几个分区id就创建几个xxxMapTask对象。partitionsToCompute是stage.findMissingPartitions()的返回值,那么查看其源码,stage是一个抽象类的引用,调用的这个方法具体的实现在具体的xxxMapStage类中。分别查看一下在resultstage和中的源码:

ResultStage:

override def findMissingPartitions(): Seq[Int] = {
  val job = activeJob.get
  (0 until job.numPartitions).filter(id => !job.finished(id))
}

ShuffleMapStage:

override def findMissingPartitions(): Seq[Int] = {
  mapOutputTrackerMaster
    .findMissingPartitions(shuffleDep.shuffleId)
    .getOrElse(0 until numPartitions)
}

​ 所以可以看出,partitionsToCompute就是一个分区索引的集合。ResultStage和ShuffleMapStage的numPartitions的值计算方式一样,都是来自于它们所处阶段的最后一个rdd的分区数量值:

job.numPartitions值:

val numPartitions = finalStage match {
  case r: ResultStage => r.partitions.length
  case m: ShuffleMapStage => m.rdd.partitions.length
}

numPartitions:

val numPartitions = rdd.partitions.length

所以总结一下,应用程序的总任务数量等于每个阶段的最后一个rdd的分区数量之和。

posted @ 2021-01-09 10:13  有心有梦  阅读(299)  评论(0编辑  收藏  举报