上篇博客中写了关于Stage划分(Stage划分源码),这次把我对Task分配算法的理解给大家分享一下。
点击上篇博客中最后那个代码的TaskScheduler.submitTasks,会发现这里定义了一个方法,但并没有实现
def submitTasks(taskSet: TaskSet): Unit
点击进去,并选择第三个
接下来继续点击super后面的submitTasks
查看代码会发现,在这里针对每一个TaskSet都生成了一个TaskSetManager,这里的TaskSetManager就如同一个保姆一样,它会去管理每个Task,并帮助Task去完成之后的Task分配,以及如果Task提交失败的话,它也会将Task重新提交。
override def submitTasks(taskSet: TaskSet) {
//存到Task数组中
val tasks = taskSet.tasks
logInfo("Adding task set " + taskSet.id + " with " + tasks.length + " tasks")
this.synchronized {
// 创建TaskSetManager,它会跟踪每个task,如果有失败的task就根据重试次数重新提交
// 还包括计算数据本地化,构建TaskDescription等
val manager = createTaskSetManager(taskSet, maxTaskFailures)
val stage = taskSet.stageId
//下面一系列操作就是判断Task是否有Task任务进来
val stageTaskSets =
taskSetsByStageIdAndAttempt.getOrElseUpdate(stage, new HashMap[Int, TaskSetManager])
stageTaskSets(taskSet.stageAttemptId) = manager
val conflictingTaskSet = stageTaskSets.exists { case (_, ts) =>
ts.taskSet != taskSet && !ts.isZombie
}
// TaskSet有冲突情况抛错(也就是Task任务执行失败)
if (conflictingTaskSet) {
throw new IllegalStateException(s"more than one active taskSet for stage $stage:" +
s" ${stageTaskSets.toSeq.map{_._2.taskSet.id}.mkString(",")}")
}
// 默认使用的是FIFO调度器(另一种是FAIR公平调度器)
schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties)
if (!isLocal && !hasReceivedTask) {
starvationTimer.scheduleAtFixedRate(new TimerTask() {
//在这里会调用Task Run这个方法执行任务
override def run() {
// 如果没运行说明集群的资源被占用 还没空闲出来
if (!hasLaunchedTask) {
logWarning("Initial job has not accepted any resources; " +
"check your cluster UI to ensure that workers are registered " +
"and have sufficient resources")
} else {
// 如果提交的Task运行了 就关闭这个Timer线程
// 释放资源
this.cancel()
}
}
}, STARVATION_TIMEOUT_MS, STARVATION_TIMEOUT_MS)
}
hasReceivedTask = true
}
//开始匹配资源,注意这个Backend就是向Master提交Application的,它是SparkContext创建时,就创建出来的CoarseGrainedSchedulerBackend
backend.reviveOffers()
}
当我们点击reviveOffers时,会发现它也是个抽象方法,而且它是在SchedulerBackend类中
def reviveOffers(): Unit
点击SchedulerBackend可以看到CoarseGrainedSchedulerBackend,正好是之前提及到的Backend
在CoarseGrainedSchedulerBackend按两个shift搜索makeoffers,这里makeOffers方法先过滤掉死亡的executor,然后利用resourceOffers的方法执行任务分配算法,将各个task分配到executor上。在分配好后,再执行launchTasks将分配好的task发送TaskLaunch消息到对应的executor上,由executor执行task。
private def makeOffers() {
// Make sure no executor is killed while some task is launching on it
//确保没有executor节点死亡当有task传输到它上面时
val taskDescs = CoarseGrainedSchedulerBackend.this.synchronized {
// Filter out executors under killing
//过滤掉死亡的executors,剩下的为活跃的executors
val activeExecutors = executorDataMap.filterKeys(executorIsAlive)