Spark之Task构建

我们已经知道DAGScheduler通过RDD算子构建DAG,再基于RDD算子之间的宽、窄依赖来切分所涉算子,最终得到一个Stage集合。每个Stage再基于Partitioner生成Task,Task集合包装成TaskSet(可能会管道化),最终TaskSet包装成TaskSetManager。

注意: TaskSetManager实现了Schedulable的调度能力,这样把Task、Schedulable内聚到一起是种不错的设计。

 Spark中Stage有两种,对应的Task也是两种,分别是ShuffleMapStage、ShuffleMapTask和ResultStage、ResultTask

Task构建逻辑

在Task的定义小节我们已经知道Task的构造函数依赖stageid、partitionid、attemptNumber等属性,属性值基本上都可以从Stage、Job中获得。另外,Task是基于Stage构建出来的,每个Stage(包括ShuffleMapStage和ResultStage)构建Task的难点在Stage在这次调度中需要构建多少个Task。上面也提到过Stage的Partitioner数量决定了要构建Task的数量,正常情况下Stage的Partitioner数量与Task数量保持一致。在实际实现层面需要考虑Stage重新调度、Task重新调度、Speculative Task之类的情况,每次构建并不能只看Partitioner数量。

DAGScheduler类的submitMissingTasks方法用于生成Task,源码中的方法体比较长就不全复制了,findMissingPartitionstaskIdToLocations两个方法。

findMissingPartitions决定构建Task的数量,taskIdToLocations决定构建Task的Executor。

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

// findMissingPartitions返回的partitionsToCompute决定构建多少个Task
// 1. 获取MAP侧的missing的 partition,在第一次没有执行的时候,所有的MAP侧都是missing。这个很好的解决了先执行M侧,再执行R侧的逻辑
// 2. 即RDD的逻辑执行计划和物理执行计划是反的,由findMissingPartitions实现,
// 3. 如果为ResultStage,这里返回的是RDD的partition
val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()

// 广播RDD和ShuffleDependency给Executor

taskBinary = sc.broadcast(taskBinaryBytes)

//根据partitionsToCompute和Stage构建Task

val tasks: Seq[Task[_]] = try {
  val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
  stage match {
    case stage: ShuffleMapStage =>
      stage.pendingPartitions.clear()
      partitionsToCompute.map { id =>

//获取Task的Locations
        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)
      }
    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)
      }
  }
} catch {
  case NonFatal(e) =>
    abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}", Some(e))
    runningStages -= stage
    return
}

}

findMissingPartitions

ShuffelMapStage和ResultStage分别实现了findMissingPartitions方法

ShuffleMapStage:

ShuffleMapStage的findMissingPartitions最终调用MapOutputTracker的findMissingPartitions。shuffleStatuses存储ShuffleStatus

// 一次Shuffle对应一个ShuffleStatus,key:为Driver侧自增长shuffleID,一个ShuffleDependency对应一个ShuffleStatus
val shuffleStatuses = new ConcurrentHashMap[Int, ShuffleStatus]().asScala

//根据shuffleStatuses记录每次shuffle的状态

//ShuffleId由SparkContext的nextShuffleId属性自增长生成

def findMissingPartitions(shuffleId: Int): Option[Seq[Int]] = {

//寻找本次Shuffle没有输出结果的partitions
  shuffleStatuses.get(shuffleId).map(_.findMissingPartitions())
}

ShuffleStatus的定义:

有一个重要的属性mapStatuses,记录shuffle操作中每个partitions的输出结果,结果使用MapStatus对象存储信息。由ShuffleMapTask返回给Driver。

// 记录每个Partitions的Map侧状态
val mapStatuses = new Array[MapStatus](numPartitions)

一次ShuffleMapStage对应一个ShuffleStatus,一个ShuffleStatus对应N个mapStatuses,N的数值与Stage的Partitions相等

def findMissingPartitions(): Seq[Int] = synchronized {

  //查找没有MapStatus有partitions

  val missing = (0 until numPartitions).filter(id => mapStatuses(id) == null)

  assert(missing.size == numPartitions - _numAvailableOutputs,

    s"${missing.size} missing, expected ${numPartitions - _numAvailableOutputs}")

  missing

}

 

综上述,ShuffleMapStage的findMissingPartitions方法通过MapOutputTracker寻找Stage中没有计算结果的Partitions的数量(也可理解Driver侧寻找ShuffleMapStage中哪些Task没有计算成功,每个Task计算成功后MapOutputTrackerMaster会把在ShuffleStatus中标识为成功)。

ResultStage

这个逻辑简单些,直接查看ActiveJob哪些Partitions所对应的finished,直接返回相应的Partition 序列集合即可。

override def findMissingPartitions(): Seq[Int] = {
  //获取当前的activeJob
  val job = activeJob.get
  //取出Job中未完成的Partition
  (0 until job.numPartitions).filter(id => !job.finished(id))
}

ActiveJob中的Partition对应的Task是否已经完成在DAGScheduler的handleTaskCompletion中完成。Executor侧发出StatusUpdate的消息给Driver侧,最终判断是ResultTask,就在DAGScheduler中更新stageIdToStage中相应的Stage和Stage对应的ActiveJob

private[scheduler] val stageIdToStage = new HashMap[Int, Stage]

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值