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在不同模式下有對應的實現方式,實質就是其有不同子類。
未完待續。。。