(七)Spark源碼理解之TaskScheduler----part4

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

未完待續。。。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章