3.2 Spark調度機制
Spark調度機制是保證Spark應用高效執行的關鍵。本節從Application、job、stage和task的維度,從上層到底層來一步一步揭示Spark的調度策略。
3.2.1 Application的調度
Spark中,每個Application對應一個SparkContext。SparkContext之間的調度關系取決于Spark的運行模式。對Standalone模式而言,Spark Master節點先計算集群內的計算資源能否滿足等待隊列中的應用對內存和CPU資源的需求,如果可以,則Master創建Spark Driver,啟動應用的執行。宏觀上來講,這種對應用的調度類似于FIFO策略。在Mesos和YARN模式下,底層的資源調度系統的調度策略都是由Mesos和YARN決定的。具體分類描述如下:
- Standalone模式
默認以用戶提交Application的順序來調度,即FIFO策略。每個應用執行時獨占所有資源。如果有多個用戶要共享集群資源,則可以使用參數spark.cores.max來配置應用在集群中可以使用的最大CPU核數。如果不配置,則采用默認參數spark.deploy. defaultCore的值來確定。
- Mesos模式
如果在Mesos上運行Spark,用戶想要靜態配置資源的話,可以設置spark.mesos. coarse為true,這樣Mesos變為粗粒度調度模式,然后可以設置spark.cores.max指定集群中可以使用的最大核數,與上面的Standalone模式類似。同時,在Mesos模式下,用戶還可以設置參數spark.executor.memory來配置每個executor的內存使用量。如果想使Mesos在細粒度模式下運行,可以通過mesos://設置動態共享cpu core的執行模式。在這種模式下,應用不執行時的空閑CPU資源得以被其他用戶使用,提升了CPU使用率。
- YARN模式
如果在YARN上運行Spark,用戶可以在YARN的客戶端上設置--num-executors來控制為應用分配的Executor數量,然后設置--executor-memory指定每個Executor的內存大小,設置--executor-cores指定Executor占用的CPU核數。
3.2.2 job的調度
前面章節提到過,Spark應用程序實際上是一系列對RDD的操作,這些操作直至遇見Action算子,才觸發Job的提交。事實上,在底層實現中,Action算子最后調用了runJob函數提交Job給Spark。其他的操作只是生成對應的RDD關系鏈。如在RDD. scala程序文件中,count函數源碼所示。
def count(): Long = sc.runJob(this, Utils.getIteratorSize _).sum
其中sc為SparkContext的對象。可見在Spark中,對Job的提交都是在Action算子中隱式完成的,并不需要用戶顯式地提交作業。在SparkContext中Job提交的實現中,最后會調用DAGScheduler中的Job提交接口。DAGScheduler最重要的任務之一就是計算Job與Task的依賴關系,制定調度邏輯。
Job調度的基本工作流程如圖3-4所示,每個Job從提交到完成,都要經歷一系列步驟,拆分成以Tsk為最小單位,按照一定邏輯依賴關系的執行序列。
[插圖]
圖3-4 Job的調度流程
圖3-5則從Job調度流程中的細節模塊出發,揭示了工作流程與對應模塊之間的關系。從整體上描述了各個類在Job調度流程中的交互關系。
[插圖]
圖3-5 Job調度流程細節
在Spark1.5.0的調度目錄下的SchedulingAlgorithm.scala文件中,描述了Spark對Job的調度模式。
- FIFO模式
默認情況下,Spark對Job以FIFO(先進先出)的模式進行調度。在SchedulingAlgorithm. scala文件中聲明了FIFO算法實現。
- FAIR模式
Spark在FAIR的模式下,采用輪詢的方式為多個Job分配資源,調度Job。所有的任務優先級大致相同,共享集群計算資源。具體實現代碼在SchedulingAlgorithm.scala文件中,聲明如下:
3.配置調度池
DAGScheduler構建了具有依賴關系的任務集。TaskScheduler負責提供任務給Task-SetManager作為調度的先決條件。TaskSetManager負責具體任務集內部的調度任務。調度池(pool)則用于調度每個SparkContext運行時并存的多個互相獨立無依賴關系的任務集。調度池負責管理下一級的調度池和TaskSetManager對象。
用戶可以通過配置文件定義調度池的屬性。一般調度池支持如下3個參數:
1)調度模式Scheduling mode:用戶可以設置FIFO或者FAIR調度方式。
2)weight:調度池的權重,在獲取集群資源上權重高的可以獲取多個資源。
3)miniShare:代表計算資源中的CPU核數。
用戶可以通過conf/fairscheduler.xml配置調度池的屬性,同時要在SparkConf對象中配置屬性。
3.2.3 stage(調度階段)和TasksetManager的調度
- Stage劃分
當一個Job被提交后,DAGScheduler會從RDD依賴鏈的末端觸發,遍歷整個RDD依賴鏈,劃分Stage(調度階段)。劃分依據主要基于ShuffleDependency依賴關系。換句話說,當某RDD在計算中需要將數據進行Shuffle操作時,這個包含Shuffle操作的RDD將會被用來作為輸入信息,構成一個新的Stage。以這個基準作為劃分Stage,可以保證存在依賴關系的數據按照正確數據得到處理和運算。在Spark1.5.0的源代碼中,DAGScheduler.scala中的getParentStages函數的實現從一定角度揭示了Stage的劃分邏輯。
- Stage調度
在第一步的Stage劃分過程中,會產生一個或者多個互相關聯的Stage。其中,真正執行Action算子的RDD所在的Stage被稱為Final Stage。DAGScheduler會從這個final stage生成作業實例。
在Stage提交時,DAGScheduler首先會判斷該Stage的父Stage的執行結果是否可用。如果所有父Stage的執行結果都可用,則提交該Stage。如果有任意一個父Stage的結果不可用,則嘗試迭代提交該父Stage。所有結果不可用的Stage都將會被加入waiting隊列,等待執行,如圖3-6所示。
[插圖]
圖3-6 Stage依賴
在圖3-6中,虛箭頭表示依賴關系。Stage序號越小,表示Stage越靠近上游。
圖3-6中的Stage調度運行順序如圖3-7所示。
[插圖]
圖3-7 Stage執行順序
從圖3-7可以看出,上游父Stage先得到執行,waiting queue中的stage隨后得到執行。
- TasksetManager
每個Stage的提交會被轉化為一組task的提交。DAGScheduler最終通過調用taskscheduler的接口來提交這組任務。在taskScheduler內部實現中創建了taskSetManager實例來管理任務集taskSet的生命周期。事實上可以說每個stage對應一個tasksetmanager。至此,DAGScheduler的工作基本完畢。taskScheduler在得到集群計算資源時,taskSet-Manager會分配task到具體worker節點上執行。在Spark1.5.0的taskSchedulerImpl.scala文件中,提交task的函數實現如下: 在Spark1.5.0的taskSchedulerImpl.scala文件中,提交task的函數實現如下:
當taskSetManager進入到調度池中時,會依據job id對taskSetManager排序,總體上先進入的taskSetManager先得到調度。對于同一job內的taskSetManager而言,job id較小的先得到調度。如果有的taskSetManager父Stage還未執行完,則該taskSet-Manager不會被放到調度池。
3.2.4 task的調度
在DAGScheduler.scala中,定義了函數submitMissingTasks,讀者閱讀完整實現,從中可以看到task的調度方式。