引言
上一节《TaskScheduler源码与任务提交原理浅析1》介绍了TaskScheduler的创建过程,在这一节中,我将承接《Stage生成和Stage源码浅析》中的submitMissingTasks函数继续介绍task的创建和分发工作。
DAGScheduler中的submitMissingTasks函数
如果一个Stage的所有的parent stage都已经计算完成或者存在于cache中,那么他会调用submitMissingTasks来提交该Stage所包含的Tasks。
submitMissingTasks负责创建新的Task。
Spark将由Executor执行的Task分为ShuffleMapTask和ResultTask两种。
每个Stage生成Task的时候根据Stage中的isShuffleMap标记确定是否为ShuffleMapStage,如果标记为真,则这个Stage输出的结果会经过Shuffle阶段作为下一个Stage的输入,创建ShuffleMapTask;否则是ResultStage,这样会创建ResultTask,Stage的结果会输出到Spark空间;最后,Task是通过taskScheduler.submitTasks来提交的。
计算流程
submitMissingTasks的计算流程如下:
- 首先得到RDD中需要计算的partition,对于Shuffle类型的stage,需要判断stage中是否缓存了该结果;对于Result类型的Final Stage,则判断计算Job中该partition是否已经计算完成。
- 序列化task的binary。Executor可以通过广播变量得到它。每个task运行的时候首先会反序列化。这样在不同的executor上运行的task是隔离的,不会相互影响。
- 为每个需要计算的partition生成一个task:对于Shuffle类型依赖的Stage,生成ShuffleMapTask类型的task;对于Result类型的Stage,生成一个ResultTask类型的task。
- 确保Task是可以被序列化的。因为不同的cluster有不同的taskScheduler,在这里判断可以简化逻辑;保证TaskSet的task都是可以序列化的。
- 通过TaskScheduler提交TaskSet。
部分代码
下面是submitMissingTasks判断是否为ShuffleMapStage的部分代码,其中部分参数说明在注释中:
val tasks: Seq[Task[_]] = if (stage.isShuffleMap) {
partitionsToCompute.map { id =>
val locs = getPreferredLocs(stage.rdd, id)
val part = stage.rdd.partitions(id)
//stage.id:Stage的序号
//taskBinary:这个在下面具体介绍
//part:RDD对应的partition
//locs:最适合的执行位置
new ShuffleMapTask(stage.id, taskBinary, part, locs)
}
} else {
val job = stage.resultOfJob.get
partitionsToCompute.map { id =>
val p: Int = job.partitions(id)
val part = stage.rdd.partitions(p)
val locs = getPreferredLocs(stage.rdd, p)
//p:partition索引,表示从哪个partition读取数据
//id:输出的分区索引,表示reduceID
new ResultTask(stage.id, taskBinary, part, locs, id)
}
}
关于taskBinary参数:这是RDD和ShuffleDependency的广播变量(broadcase version),作为序列化之后的结果。
这里将RDD和其依赖关系进行序列化,在executor运行task之前再进行反序列化。这种方式对不同的task之间提供了较好的隔离。
下面是submitMissingTasks进行任务提交的部分代码:
if (tasks.size >