resourceOffers():该方法是TaskSchedulerImpl的核心所在,实现将任务指定给对应的从节点中的executor,其主要思路可以概述为:
首先将获取的每个executor的资源,组成组成WorkerOffer序列,然后将其打乱,接着对每个任务集进行操作,首先判断第一个executor,如果它的CPU个数符合任务集中的任务所需,则将该任务集中挂在该executor下的任务取出,得到相应的任务描述,然后添加到任务描述的容器中,之后对第二个executor进行同样的操作。具体可以查看代码详解
讲述该代码之前简要理一下思路:
WorkerOffer指的就是每个executor上可用的资源,它有几个参数:executorId,host,CPU的个数
下面讲述代码:
def resourceOffers(offers: Seq[WorkerOffer]): Seq[Seq[TaskDescription]] = synchronized {
SparkEnv.set(sc.env)
// Mark each slave as alive and remember its hostname
for (o <- offers) {//将executor和host的关系记录在相应的容器中
executorIdToHost(o.executorId) = o.host
if (!executorsByHost.contains(o.host)) {
executorsByHost(o.host) = new HashSet[String]()
executorAdded(o.executorId, o.host)//调用了DAGScheduler的方法
}
}
// Randomly shuffle offers to avoid always placing tasks on the same set of workers.
val shuffledOffers = Random.shuffle(offers)//将序列WorkerOffer的内容打乱,避免任务总是分配给重复的几个从节点的executor
// Build a list of tasks to assign to each worker.
//为从节点的executor建立一个TaskDescription类型的ArrayBuffer,目的在于存储分配给该executor的任务
val tasks = shuffledOffers.map(o => new ArrayBuffer[TaskDescription](o.cores))
val availableCpus = shuffledOffers.map(o => o.cores).toArray//将各个executor的CPU的个数单独取出来,组成一个数组
val sortedTaskSets = rootPool.getSortedTaskSetQueue//从线程池中取出以及排好序的任务集
for (taskSet <- sortedTaskSets) {
logDebug("parentName: %s, name: %s, runningTasks: %s".format(
taskSet.parent.name, taskSet.name, taskSet.runningTasks))
}
// Take each TaskSet in our scheduling order, and then offer it each node in increasing order
// of locality levels so that it gets a chance to launch local tasks on all of them.
var launchedTask = false
for (taskSet <- sortedTaskSets; maxLocality <- TaskLocality.values) {
do {
launchedTask = false//设置装在任务的标志位假
for (i <- 0 until shuffledOffers.size) {//逐个逐个给每个从节点的executor分配任务,如若某executor的CPU个数不满足任务所需的CPU时则轮到下一个executor
val execId = shuffledOffers(i).executorId//获取executor的id
val host = shuffledOffers(i).host//获取executor的host
if (availableCpus(i) >= CPUS_PER_TASK) {//如果该executor的CPU个数满足任务所需的最大CPU的个数,则可以分配,否则,就将任务分配给下一个executor
for (task <- taskSet.resourceOffer(execId, host, maxLocality)) {//调用TaskSetManager的resourceOffer()方法,获得任务的任务描述
tasks(i) += task//将任务描述添加到该executor的任务描述容器中
val tid = task.taskId
taskIdToTaskSetId(tid) = taskSet.taskSet.id//将taskId和TaskSetId对应起来
taskIdToExecutorId(tid) = execId//将taskId和executorId对应起来
activeExecutorIds += execId
executorsByHost(host) += execId
availableCpus(i) -= CPUS_PER_TASK//将该executor的CPU个数减少
assert (availableCpus(i) >= 0)
launchedTask = true
}
}
}
} while (launchedTask)
}
if (tasks.size > 0) {
hasLaunchedTask = true
}
return tasks//返回最终结果
}
3.1 Schedulable
Schedulable类其实就是可调度对象,具有两种类型:线程池和TaskSetManager任务集管理器,可以联想到上面我讲过的任务是由线程去执行的。
该类中主要就是定义了添加,移除可调度对象等方法
3.2 SchedulableBuilder
SchedulableBuilder的作用是定义一颗可调度对象的树,实际上可以理解为用来管理线程池和任务集管理器的,它有个最重要的方法就是:addTaskSetManager(manager:Schedulable, properties: Properties)
实现在线程池中添加TaskSetManager
3.3 SchedulerBackend
SchedulerBackend可以认为是TaskSchedulerImpl实现的后台支撑,为它提供各种调度服务。其中最主要的方法就是reviveOffers(),这个方法可以理解为重新生成相应executor的WorkerOffer,然后分配任务给executor,获得指定给该executor的任务描述,最后executor装载该任务,在TaskSchedulerImpl的多个方法中都调用了此方法,此外SchedulerBackend在不同模式下有对应的实现方式,实质就是其有不同子类。
未完待续。。。