我们已经知道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,源码中的方法体比较长就不全复制了,findMissingPartitions、taskIdToLocations两个方法。
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]