引言
上一節《Stage生成和Stage源碼淺析》中,我介紹了Stage生成劃分到提交Stage的過程,分析最終歸結到submitStage的遞歸提交Stage,其中要通過submitMissingTasks函數創建task集合來實現任務的創建和分發。
在接下來的幾篇文章中,我將具體介紹一下任務創建和分發的過程,為了讓邏輯更加清楚,我將分成幾篇文章進行介紹,好保證簡明清晰,邏輯連貫,前后統一。
TaskScheduler介紹
TaskScheduler的主要任務是提交taskset到集群運算并匯報結果。
具體而言:
- 出現shuffle輸出lost要報告fetch failed錯誤
- 碰到straggle任務需要放到別的節點上重試
- 為每個TaskSet維護一個TaskSetManager(追蹤本地性及錯誤信息)
TaskScheduler創建
在《SparkContext源碼解讀》一文中,我介紹了在SparkContext初始化時創建TaskScheduler和DAGScheduler。這里具體描述一下其創建過程。
SparkContext創建過程中會調用createTaskScheduler函數來啟動TaskScheduler任務調度器:
// Create and start the scheduler
private[spark] var (schedulerBackend, taskScheduler) =
SparkContext.createTaskScheduler(this, master)
createTaskScheduler函數中,TaskScheduler會根據部署方式而選擇不同的SchedulerBackend來處理.
針對不同部署方式會有不同的TaskScheduler與SchedulerBackend進行組合:
- Local模式:TaskSchedulerImpl + LocalBackend
- Spark集群模式:TaskSchedulerImpl + SparkDepolySchedulerBackend
- Yarn-Cluster模式:YarnClusterScheduler + CoarseGrainedSchedulerBackend
- Yarn-Client模式:YarnClientClusterScheduler + YarnClientSchedulerBackend
/**
* Create a task scheduler based on a given master URL.
* Return a 2-tuple of the scheduler backend and the task scheduler.
*/
private def createTaskScheduler(
sc: SparkContext,
master: String): (SchedulerBackend, TaskScheduler) = {
// Regular expression used for local[N] and local[*] master formats
val LOCAL_N_REGEX = """local\[([0-9]+|\*)\]""".r
// Regular expression for local[N, maxRetries], used in tests with failing tasks
val LOCAL_N_FAILURES_REGEX = """local\[([0-9]+|\*)\s*,\s*([0-9]+)\]""".r
// Regular expression for simulating a Spark cluster of [N, cores, memory] locally
val LOCAL_CLUSTER_REGEX = """local-cluster\[\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*]""".r
// Regular expression for connecting to Spark deploy clusters
val SPARK_REGEX = """spark://(.*)""".r
// Regular expression for connection to Mesos cluster by mesos:// or zk:// url
val MESOS_REGEX = """(mesos|zk)://.*""".r
// Regular expression for connection to Simr cluster
val SIMR_REGEX = """simr://(.*)""".r
// When running locally, don't try to re-execute tasks on failure.
val MAX_LOCAL_TASK_FAILURES = 1
master match {
case "local" =>
val scheduler = new TaskSchedulerImpl(sc, MAX_LOCAL_TASK_FAILURES, isLocal = true)
val backend = new LocalBackend(scheduler, 1)
scheduler.initialize(backend)
(backend, scheduler)
case LOCAL_N_REGEX(threads) =>
...
case LOCAL_N_FAILURES_REGEX(threads, maxFailures) =>
...
case SPARK_REGEX(sparkUrl) =>
val scheduler = new TaskSchedulerImpl(sc)
val masterUrls = sparkUrl.split(",").map("spark://" + _)
val backend = new SparkDeploySchedulerBackend(scheduler, sc, masterUrls)
scheduler.initialize(backend)
(backend, scheduler)
case LOCAL_CLUSTER_REGEX(numSlaves, coresPerSlave, memoryPerSlave) =>
// Check to make sure memory requested <= memoryPerSlave. Otherwise Spark will just hang.
val memoryPerSlaveInt = memoryPerSlave.toInt
if (sc.executorMemory > memoryPerSlaveInt) {
throw new SparkException(
"Asked to launch cluster with %d MB RAM / worker but requested %d MB/worker".format(
memoryPerSlaveInt, sc.executorMemory))
}
val scheduler = new TaskSchedulerImpl(sc)
val localCluster = new LocalSparkCluster(
numSlaves.toInt, coresPerSlave.toInt, memoryPerSlaveInt, sc.conf)
val masterUrls = localCluster.start()
val backend = new SparkDeploySchedulerBackend(scheduler, sc, masterUrls)
scheduler.initialize(backend)
backend.shutdownCallback = (backend: SparkDeploySchedulerBackend) => {
localCluster.stop()
}
(backend, scheduler)
.....
以Standalone模式為例,backend根據不同的部署方式實例化,后又作為scheduler對象的一個成員變量對scheduler調用initialize函數:
case SPARK_REGEX(sparkUrl) =>
val scheduler = new TaskSchedulerImpl(sc)
val masterUrls = sparkUrl.split(",").map("spark://" + _)
val backend = new SparkDeploySchedulerBackend(scheduler, sc, masterUrls)
scheduler.initialize(backend)
(backend, scheduler)
TaskScheduler、TaskSchedulerImpl、SchedulerBackend之間的關系
TaskScheduler類負責任務調度資源的分配,SchedulerBackend負責與Master、Worker通信收集Worker上分配給該應用使用的資源情況。
下圖描述了TaskScheduler、TaskSchedulerImpl、SchedulerBackend之間的UML關系,其中TaskSchedulerImpl是task schduler的具體實現,其中混入了TaskScheduler特質,而SparkDeploySchedulerBackend等具體的資源收集類繼承自CoarseGrainedSchedulerBackend這一父類,而CoarseGrainedSchedulerBackend混入了SchedulerBackend特質:

這里還是以Spark Standalone集群模式為例,分析TaskSchedulerImpl與SparkDepolySchedulerBackend類中的具體操作。
資源信息收集
SparkDepolySchedulerBackend類就是專門負責收集Worker的資源信息,在它的父類CoarseGrainedSchedulerBackend中的DriverActor就是與Worker通信的Actor。
Worker啟動后會向Driver發送RegisterExecutor消息,此消息中就包含了Executor為Application分配的計算資源信息,而接收該消息的Actor也正是DriverActor。資源分配
TaskSchedulerImpl類就是負責為Task分配資源的。在CoarseGrainedSchedulerBackend獲取到可用資源后就會通過makeOffers
方法通知TaskSchedulerImpl對資源進行分配,TaskSchedulerImpl的resourceOffers
方法就是負責為Task分配計算資源的,在為Task分配好資源后又會通過lauchTasks方法發送LaunchTask消息通知Worker上的Executor執行Task。
TaskScheduler創建中函數調用鏈
SparkContext的createTaskScheduler
創建schedulerBackend和taskScheduler-->根據不同的調度方式選擇具體的scheduler和backend構造器-->調用TaskSchedulerImpl的initialize
方法為scheduler的成員變量backend賦值-->createTaskScheduler
返回創建好的(schedulerBackend, taskScheduler)
-->調用TaskScheduler.start()
啟動-->實際上在TaskSchedulerImpl的start方法中調用backend.start()
來啟動SchedulerBackend。
TaskScheduler是在Application執行過程中,為它進行任務調度的,是屬于Driver側的。對應于一個Application就會有一個TaskScheduler,TaskScheduler和Application是一一對應的。TaskScheduler對資源的控制也比較魯棒,一個Application申請Worker的計算資源,只要Application不結束就會一直被占有。
小結
這一篇文章,我們介紹了TaskScheduler的創建過程,TaskScheduler、TaskSchedulerImpl、SchedulerBackend之間的關系還有創建過程的調用鏈,給大家一個初始印象。在下一篇中,我將承接Stage劃分完畢后進行task創建和分發流程,進行細致的介紹。
參考資料
轉載請注明作者Jason Ding及其出處
GitCafe博客主頁(http://jasonding1354.gitcafe.io/)
Github博客主頁(http://jasonding1354.github.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡書主頁(http://www.lxweimin.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354進入我的博客主頁