通過文章“Spark核心概念RDD”我們知道,Spark的核心是根據(jù)RDD來實現(xiàn)的,Spark Scheduler則為Spark核心實現(xiàn)的重要一環(huán),其作用就是任務(wù)調(diào)度。Spark的任務(wù)調(diào)度就是如何組織任務(wù)去處理RDD中每個分區(qū)的數(shù)據(jù),根據(jù)RDD的依賴關(guān)系構(gòu)建DAG,基于DAG劃分Stage,將每個Stage中的任務(wù)發(fā)到指定節(jié)點運行。基于Spark的任務(wù)調(diào)度原理,我們可以合理規(guī)劃資源利用,做到盡可能用最少的資源高效地完成任務(wù)計算。
分布式運行框架
Spark可以部署在多種資源管理平臺,例如Yarn、Mesos等,Spark本身也實現(xiàn)了一個簡易的資源管理機制,稱之為Standalone模式。由于工作中接觸較多的是Saprk on Yarn,不做特別說明,以下所述均表示Spark-on-Yarn。Spark部署在Yarn上有兩種運行模式,分別為yarn-client和yarn-cluster模式,它們的區(qū)別僅僅在于Spark Driver是運行在Client端還是ApplicationMater端。如下圖所示為Spark部署在Yarn上,以yarn-cluster模式運行的分布式計算框架。
其中藍色部分是Spark里的概念,包括Client、ApplicationMaster、Driver和Executor,其中Client和ApplicationMaster主要是負責(zé)與Yarn進行交互;Driver作為Spark應(yīng)用程序的總控,負責(zé)分發(fā)任務(wù)以及監(jiān)控任務(wù)運行狀態(tài);Executor負責(zé)執(zhí)行任務(wù),并上報狀態(tài)信息給Driver,從邏輯上來看Executor是進程,運行在其中的任務(wù)是線程,所以說Spark的任務(wù)是線程級別的。通過下面的時序圖可以更清晰地理解一個Spark應(yīng)用程序從提交到運行的完整流程。
提交一個Spark應(yīng)用程序,首先通過Client向ResourceManager請求啟動一個Application,同時檢查是否有足夠的資源滿足Application的需求,如果資源條件滿足,則準(zhǔn)備ApplicationMaster的啟動上下文,交給ResourceManager,并循環(huán)監(jiān)控Application狀態(tài)。
當(dāng)提交的資源隊列中有資源時,ResourceManager會在某個NodeManager上啟動ApplicationMaster進程,ApplicationMaster會單獨啟動Driver后臺線程,當(dāng)Driver啟動后,ApplicationMaster會通過本地的RPC連接Driver,并開始向ResourceManager申請Container資源運行Executor進程(一個Executor對應(yīng)與一個Container),當(dāng)ResourceManager返回Container資源,則在對應(yīng)的Container上啟動Executor。
Driver線程主要是初始化SparkContext對象,準(zhǔn)備運行所需的上下文,然后一方面保持與ApplicationMaster的RPC連接,通過ApplicationMaster申請資源,另一方面根據(jù)用戶業(yè)務(wù)邏輯開始調(diào)度任務(wù),將任務(wù)下發(fā)到已有的空閑Executor上。
當(dāng)ResourceManager向ApplicationMaster返回Container資源時,ApplicationMaster就嘗試在對應(yīng)的Container上啟動Executor進程,Executor進程起來后,會向Driver注冊,注冊成功后保持與Driver的心跳,同時等待Driver分發(fā)任務(wù),當(dāng)分發(fā)的任務(wù)執(zhí)行完畢后,將任務(wù)狀態(tài)上報給Driver。
Driver把資源申請的邏輯給抽象出來,以適配不同的資源管理系統(tǒng),所以才間接地通過ApplicationMaster去和Yarn打交道。
從上述時序圖可知,Client只管提交Application并監(jiān)控Application的狀態(tài)。對于Spark的任務(wù)調(diào)度主要是集中在兩個方面: 資源申請和任務(wù)分發(fā),其主要是通過ApplicationMaster、Driver以及Executor之間來完成,下面詳細剖析Spark任務(wù)調(diào)度每個細節(jié)。
Spark任務(wù)調(diào)度總覽
當(dāng)Driver起來后,Driver則會根據(jù)用戶程序邏輯準(zhǔn)備任務(wù),并根據(jù)Executor資源情況逐步分發(fā)任務(wù)。在詳細闡述任務(wù)調(diào)度前,首先說明下Spark里的幾個概念。一個Spark應(yīng)用程序包括Job、Stage以及Task三個概念:
- Job是以Action方法為界,遇到一個Action方法則觸發(fā)一個Job;
- Stage是Job的子集,以RDD寬依賴(即Shuffle)為界,遇到Shuffle做一次劃分;
- Task是Stage的子集,以并行度(分區(qū)數(shù))來衡量,分區(qū)數(shù)是多少,則有多少個task。
Spark的任務(wù)調(diào)度總體來說分兩路進行,一路是Stage級的調(diào)度,一路是Task級的調(diào)度,總體調(diào)度流程如下圖所示。
Spark RDD通過其Transactions操作,形成了RDD血緣關(guān)系圖,即DAG,最后通過Action的調(diào)用,觸發(fā)Job并調(diào)度執(zhí)行。DAGScheduler負責(zé)Stage級的調(diào)度,主要是將DAG切分成若干Stages,并將每個Stage打包成TaskSet交給TaskScheduler調(diào)度。TaskScheduler負責(zé)Task級的調(diào)度,將DAGScheduler給過來的TaskSet按照指定的調(diào)度策略分發(fā)到Executor上執(zhí)行,調(diào)度過程中SchedulerBackend負責(zé)提供可用資源,其中SchedulerBackend有多種實現(xiàn),分別對接不同的資源管理系統(tǒng)。有了上述感性的認識后,下面這張圖描述了Spark-On-Yarn模式下在任務(wù)調(diào)度期間,ApplicationMaster、Driver以及Executor內(nèi)部模塊的交互過程。
Driver初始化SparkContext過程中,會分別初始化DAGScheduler、TaskScheduler、SchedulerBackend以及HeartbeatReceiver,并啟動SchedulerBackend以及HeartbeatReceiver。SchedulerBackend通過ApplicationMaster申請資源,并不斷從TaskScheduler中拿到合適的Task分發(fā)到Executor執(zhí)行。HeartbeatReceiver負責(zé)接收Executor的心跳信息,監(jiān)控Executor的存活狀況,并通知到TaskScheduler。下面著重剖析DAGScheduler負責(zé)的Stage調(diào)度以及TaskScheduler負責(zé)的Task調(diào)度。
Stage級的調(diào)度
Spark的任務(wù)調(diào)度是從DAG切割開始,主要是由DAGScheduler來完成。當(dāng)遇到一個Action操作后就會觸發(fā)一個Job的計算,并交給DAGScheduler來提交,下圖是涉及到Job提交的相關(guān)方法調(diào)用流程圖。
Job由最終的RDD和Action方法封裝而成,SparkContext將Job交給DAGScheduler提交,它會根據(jù)RDD的血緣關(guān)系構(gòu)成的DAG進行切分,將一個Job劃分為若干Stages,具體劃分策略是,由最終的RDD不斷通過依賴回溯判斷父依賴是否是款依賴,即以Shuffle為界,劃分Stage,窄依賴的RDD之間被劃分到同一個Stage中,可以進行pipeline式的計算,如上圖紫色流程部分。劃分的Stages分兩類,一類叫做ResultStage,為DAG最下游的Stage,由Action方法決定,另一類叫做ShuffleMapStage,為下游Stage準(zhǔn)備數(shù)據(jù),下面看一個簡單的例子WordCount。
Job由saveAsTextFile
觸發(fā),該Job由RDD-3和saveAsTextFile
方法組成,根據(jù)RDD之間的依賴關(guān)系從RDD-3開始回溯搜索,直到?jīng)]有依賴的RDD-0,在回溯搜索過程中,RDD-3依賴RDD-2,并且是寬依賴,所以在RDD-2和RDD-3之間劃分Stage,RDD-3被劃到最后一個Stage,即ResultStage中,RDD-2依賴RDD-1,RDD-1依賴RDD-0,這些依賴都是窄依賴,所以將RDD-0、RDD-1和RDD-2劃分到同一個Stage,即ShuffleMapStage中,實際執(zhí)行的時候,數(shù)據(jù)記錄會一氣呵成地執(zhí)行RDD-0到RDD-2的轉(zhuǎn)化。不難看出,其本質(zhì)上是一個深度優(yōu)先搜索算法。
一個Stage是否被提交,需要判斷它的父Stage是否執(zhí)行,只有在父Stage執(zhí)行完畢才能提交當(dāng)前Stage,如果一個Stage沒有父Stage,那么從該Stage開始提交。Stage提交時會將Task信息(分區(qū)信息以及方法等)序列化并被打包成TaskSet交給TaskScheduler,一個Partition對應(yīng)一個Task,另一方面監(jiān)控Stage的運行狀態(tài),只有Executor丟失或者Task由于Fetch失敗才需要重新提交失敗的Stage以調(diào)度運行失敗的任務(wù),其他類型的Task失敗會在TaskScheduler的調(diào)度過程中重試。
相對來說DAGScheduler做的事情較為簡單,僅僅是在Stage層面上劃分DAG,提交Stage并監(jiān)控相關(guān)狀態(tài)信息。TaskScheduler則相對較為復(fù)雜,下面詳細闡述其細節(jié)。
Task級的調(diào)度
Spark Task的調(diào)度是由TaskScheduler來完成,由前文可知,DAGScheduler將Stage打包到TaskSet交給TaskScheduler,TaskScheduler會將其封裝為TaskSetManager加入到調(diào)度隊列中,TaskSetManager負責(zé)監(jiān)控管理同一個Stage中的Tasks,TaskScheduler就是以TaskSetManager為單元來調(diào)度任務(wù)。前面也提到,TaskScheduler初始化后會啟動SchedulerBackend,它負責(zé)跟外界打交道,接收Executor的注冊信息,并維護Executor的狀態(tài),所以說SchedulerBackend是管“糧食”的,同時它在啟動后會定期地去“詢問”TaskScheduler有沒有任務(wù)要運行,也就是說,它會定期地“問”TaskScheduler“我有這么余量,你要不要啊”,TaskScheduler在SchedulerBackend“問”它的時候,會從調(diào)度隊列中按照指定的調(diào)度策略選擇TaskSetManager去調(diào)度運行,大致方法調(diào)用流程如下圖所示。
調(diào)度策略
前面講到,TaskScheduler會先把DAGScheduler給過來的TaskSet封裝成TaskSetManager扔到任務(wù)隊列里,然后再從任務(wù)隊列里按照一定的規(guī)則把它們?nèi)〕鰜碓赟chedulerBackend給過來的Executor上運行。這個調(diào)度過程實際上還是比較粗粒度的,是面向TaskSetManager的。
TaskScheduler是以樹的方式來管理任務(wù)隊列,樹中的節(jié)點類型為Schdulable,葉子節(jié)點為TaskSetManager,非葉子節(jié)點為Pool,下圖是它們之間的繼承關(guān)系。
TaskScheduler支持兩種調(diào)度策略,一種是FIFO,也是默認的調(diào)度策略,另一種是FAIR。在TaskScheduler初始化過程中會實例化rootPool,表示樹的根節(jié)點,是Pool類型。如果是采用FIFO調(diào)度策略,則直接簡單地將TaskSetManager按照先來先到的方式入隊,出隊時直接拿出最先進隊的TaskSetManager,其樹結(jié)構(gòu)大致如下圖所示,TaskSetManager保存在一個FIFO隊列中。
在闡述FAIR調(diào)度策略前,先貼一段使用FAIR調(diào)度策略的應(yīng)用程序代碼,后面針對該代碼邏輯來詳細闡述FAIR調(diào)度的實現(xiàn)細節(jié)。
object MultiJobTest {
// spark.scheduler.mode=FAIR
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().getOrCreate()
val rdd = spark.sparkContext.textFile(...)
.map(_.split("\\s+"))
.map(x => (x(0), x(1)))
val jobExecutor = Executors.newFixedThreadPool(2)
jobExecutor.execute(new Runnable {
override def run(): Unit = {
spark.sparkContext.setLocalProperty("spark.scheduler.pool", "count-pool")
val cnt = rdd.groupByKey().count()
println(s"Count: $cnt")
}
})
jobExecutor.execute(new Runnable {
override def run(): Unit = {
spark.sparkContext.setLocalProperty("spark.scheduler.pool", "take-pool")
val data = rdd.sortByKey().take(10)
println(s"Data Samples: ")
data.foreach { x => println(x.mkString(", ")) }
}
})
jobExecutor.shutdown()
while (!jobExecutor.isTerminated) {}
println("Done!")
}
}
上述應(yīng)用程序中使用兩個線程分別調(diào)用了Action方法,即有兩個Job會并發(fā)提交,但是不管怎樣,這兩個Job被切分成若干TaskSet后終究會被交到TaskScheduler這里統(tǒng)一管理,其調(diào)度樹大致如下圖所示。
在出隊時,則會對所有TaskSetManager排序,具體排序過程是從根節(jié)點rootPool開始,遞歸地去排序子節(jié)點,最后合并到一個
ArrayBuffer
里,代碼邏輯如下。
var sortedTaskSetQueue = new ArrayBuffer[TaskSetManager]
val sortedSchedulableQueue = schedulableQueue.asScala.toSeq.sortWith(taskSetSchedulingAlgorithm.comparator)
for (schedulable <- sortedSchedulableQueue) {
sortedTaskSetQueue ++= schedulable.getSortedTaskSetQueue
}
sortedTaskSetQueue
}
使用FAIR調(diào)度策略時,上面代碼中的taskSetSchedulingAlgorithm的類型為FairSchedulingAlgorithm,排序過程的比較是基于Fair-share來比較的,每個要排序的對象包含三個屬性: runningTasks值(正在運行的Task數(shù))、minShare值、weight值,比較時會綜合考量runningTasks值,minShare以及weight值。如果A對象的runningTasks大于它的minShare,B對象的runningTasks小于它的minShare,那么B排在A前面;如果A、B對象的runningTasks都小于它們的minShare,那么就比較runningTasks與minShare的比值,誰小誰排前面;如果A、B對象的runningTasks都大于它們的minShare,那么就比較runningTasks與weight的比值,誰小誰排前面。整體上來說就是通過minShare和weight這兩個參數(shù)控制比較過程,可以做到不讓資源被某些長時間Task給一直占了。
從調(diào)度隊列中拿到TaskSetManager后,那么接下來的工作就是TaskSetManager按照一定的規(guī)則一個個取出Task給TaskScheduler,TaskScheduler再交給SchedulerBackend去發(fā)到Executor上執(zhí)行。前面也提到,TaskSetManager封裝了一個Stage的所有Task,并負責(zé)管理調(diào)度這些Task。
本地化調(diào)度
從調(diào)度隊列中拿到TaskSetManager后,那么接下來的工作就是TaskSetManager按照一定的規(guī)則一個個取出Task給TaskScheduler,TaskScheduler再交給SchedulerBackend去發(fā)到Executor上執(zhí)行。前面也提到,TaskSetManager封裝了一個Stage的所有Task,并負責(zé)管理調(diào)度這些Task。
在TaskSetManager初始化過程中,會對Tasks按照Locality級別進行分類,Task的Locality有五種,優(yōu)先級由高到低順序:PROCESS_LOCAL(指定的Executor),NODE_LOCAL(指定的主機節(jié)點),NO_PREF(無所謂),RACK_LOCAL(指定的機架),ANY(滿足不了Task的Locality就隨便調(diào)度)。這五種Locality級別存在包含關(guān)系,RACK_LOCAL包含NODE_LOCAL,NODE_LOCAL包含PROCESS_LOCAL,然而ANY包含其他所有四種。初始化階段在對Task分類時,根據(jù)Task的preferredLocations判斷它屬于哪個Locality級別,屬于PROCESS_LOCAL的Task同時也會被加入到NODE_LOCAL、RACK_LOCAL類別中,比如,一個Task的preferredLocations指定了在Executor-2上執(zhí)行,那么它屬于Executor-2對應(yīng)的PROCESS_LOCAL類別,同時也把他加入到Executor-2所在的主機對應(yīng)的NODE_LOCAL類別,Executor-2所在的主機的機架對應(yīng)的RACK_LOCAL類別中,以及ANY類別,這樣在調(diào)度執(zhí)行時,滿足不了PROCESS_LOCAL,就逐步退化到NODE_LOCAL,RACK_LOCAL,ANY。
TaskSetManager在決定調(diào)度哪些Task時,是通過上面流程圖中的resourceOffer方法來實現(xiàn),為了盡可能地將Task調(diào)度到它的preferredLocations上,它采用一種延遲調(diào)度算法。resourceOffer方法原型如下,參數(shù)包括要調(diào)度任務(wù)的Executor Id、主機地址以及最大可容忍的Locality級別。
def resourceOffer(
execId: String,
host: String,
maxLocality: TaskLocality.TaskLocality)
: Option[TaskDescription]
延遲調(diào)度算法的大致流程如下圖所示。
首先看是否存在execId對應(yīng)的PROCESS_LOCAL類別的任務(wù),如果存在,取出來調(diào)度,否則根據(jù)當(dāng)前時間,判斷是否超過了PROCESS_LOCAL類別最大容忍的延遲,如果超過,則退化到下一個級別NODE_LOCAL,否則等待不調(diào)度。退化到下一個級別NODE_LOCAL后調(diào)度流程也類似,看是否存在host對應(yīng)的NODE_LOCAL類別的任務(wù),如果存在,取出來調(diào)度,否則根據(jù)當(dāng)前時間,判斷是否超過了NODE_LOCAL類別最大容忍的延遲,如果超過,則退化到下一個級別RACK_LOCAL,否則等待不調(diào)度,以此類推…..。當(dāng)不滿足Locatity類別會選擇等待,直到下一輪調(diào)度重復(fù)上述流程,如果你比較激進,可以調(diào)大每個類別的最大容忍延遲時間,如果不滿足Locatity時就會等待多個調(diào)度周期,直到滿足或者超過延遲時間退化到下一個級別為止。
失敗重試與黑名單機制
除了選擇合適的Task調(diào)度運行外,還需要監(jiān)控Task的執(zhí)行狀態(tài),前面也提到,與外部打交道的是SchedulerBackend,Task被提交到Executor啟動執(zhí)行后,Executor會將執(zhí)行狀態(tài)上報給SchedulerBackend,SchedulerBackend則告訴TaskScheduler,TaskScheduler找到該Task對應(yīng)的TaskSetManager,并通知到該TaskSetManager,這樣TaskSetManager就知道Task的失敗與成功狀態(tài),對于失敗的Task,會記錄它失敗的次數(shù),如果失敗次數(shù)還沒有超過最大重試次數(shù),那么就把它放回待調(diào)度的Task池子中,否則整個Application失敗。
在記錄Task失敗次數(shù)過程中,會記錄它上一次失敗所在的Executor Id和Host,這樣下次再調(diào)度這個Task時,會使用黑名單機制,避免它被調(diào)度到上一次失敗的節(jié)點上,起到一定的容錯作用。黑名單記錄Task上一次失敗所在的Executor Id和Host,以及其對應(yīng)的“黑暗”時間,“黑暗”時間是指這段時間內(nèi)不要再往這個節(jié)點上調(diào)度這個Task了。
推測式執(zhí)行
TaskScheduler在啟動SchedulerBackend后,還會啟動一個后臺線程專門負責(zé)推測任務(wù)的調(diào)度,推測任務(wù)是指對一個Task在不同的Executor上啟動多個實例,如果有Task實例運行成功,則會干掉其他Executor上運行的實例。推測調(diào)度線程會每隔固定時間檢查是否有Task需要推測執(zhí)行,如果有,則會調(diào)用SchedulerBackend的reviveOffers去嘗試拿資源運行推測任務(wù)。
檢查是否有Task需要推測執(zhí)行的邏輯最后會交到TaskSetManager,TaskSetManager采用基于統(tǒng)計的算法,檢查Task是否需要推測執(zhí)行,算法流程大致如下圖所示。
TaskSetManager首先會統(tǒng)計成功的Task數(shù),當(dāng)成功的Task數(shù)超過75%(可通過參數(shù)spark.speculation.quantile控制)時,再統(tǒng)計所有成功的Tasks的運行時間,得到一個中位數(shù),用這個中位數(shù)乘以1.5(可通過參數(shù)spark.speculation.multiplier控制)得到運行時間門限,如果在運行的Tasks的運行時間超過這個門限,則對它啟用推測。算法邏輯較為簡單,其實就是對那些拖慢整體進度的Tasks啟用推測,以加速整個TaskSet即Stage的運行。
資源申請機制
在前文已經(jīng)提過,ApplicationMaster和SchedulerBackend起來后,SchedulerBackend通過ApplicationMaster申請資源,ApplicationMaster就是用來專門適配YARN申請Container資源的,當(dāng)申請到Container,會在相應(yīng)Container上啟動Executor進程,其他事情就交給SchedulerBackend。Spark早期版本只支持靜態(tài)資源申請,即一開始就指定用多少資源,在整個Spark應(yīng)用程序運行過程中資源都不能改變,后來支持動態(tài)Executor申請,用戶不需要指定確切的Executor數(shù)量,Spark會動態(tài)調(diào)整Executor的數(shù)量以達到資源利用的最大化。
靜態(tài)資源申請
靜態(tài)資源申請是用戶在提交Spark應(yīng)用程序時,就要提前估計應(yīng)用程序需要使用的資源,包括Executor數(shù)(num_executor)、每個Executor上的core數(shù)(executor_cores)、每個Executor的內(nèi)存(executor_memory)以及Driver的內(nèi)存(driver_memory)。
在估計資源使用時,應(yīng)當(dāng)首先了解這些資源是怎么用的。任務(wù)的并行度由分區(qū)數(shù)(Partitions)決定,一個Stage有多少分區(qū),就會有多少Task。每個Task默認占用一個Core,一個Executor上的所有core共享Executor上的內(nèi)存,一次并行運行的Task數(shù)等于num_executor*executor_cores,如果分區(qū)數(shù)超過該值,則需要運行多個輪次,一般來說建議運行3~5輪較為合適,否則考慮增加num_executor或executor_cores。由于一個Executor的所有tasks會共享內(nèi)存executor_memory,所以建議executor_cores不宜過大。executor_memory的設(shè)置則需要綜合每個分區(qū)的數(shù)據(jù)量以及是否有緩存等邏輯。下圖描繪了一個應(yīng)用程序內(nèi)部資源利用情況。
動態(tài)資源申請
動態(tài)資源申請目前只支持到Executor,即可以不用指定num_executor,通過參數(shù)spark.dynamicAllocation.enabled來控制。由于許多Spark應(yīng)用程序一開始可能不需要那么多Executor或者其本身就不需要太多Executor,所以不必一次性申請那么多Executor,根據(jù)具體的任務(wù)數(shù)動態(tài)調(diào)整Executor的數(shù)量,盡可能做到資源的不浪費。由于動態(tài)Executor的調(diào)整會導(dǎo)致Executor動態(tài)的添加與刪除,如果刪除Executor,其上面的中間Shuffle結(jié)果可能會丟失,這就需要借助第三方的ShuffleService了,如果Spark是部署在Yarn上,則可以在Yarn上配置Spark的ShuffleService,具體操作僅需做兩點:
1.首先在yarn-site.xml中加上如下配置:
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle,spark_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.aux-services.spark_shuffle.class</name>
<value>org.apache.spark.network.yarn.YarnShuffleService</value>
</property>
<property>
<name>spark.shuffle.service.port</name>
<value>7337</value>
</property>
- 將Spark ShuffleService jar包$SPARK_HOME/lib/spark-*-yarn-shuffle.jar拷貝到每臺NodeManager的$HADOOP_HOME/share/hadoop/yarn/lib/下,并重啟所有的NodeManager。
當(dāng)啟用動態(tài)Executor申請時,在SparkContext初始化過程中會實例化ExecutorAllocationManager,它是被用來專門控制動態(tài)Executor申請邏輯的,動態(tài)Executor申請是一種基于當(dāng)前Task負載壓力實現(xiàn)動態(tài)增刪Executor的機制。一開始會按照參數(shù)spark.dynamicAllocation.initialExecutors設(shè)置的初始Executor數(shù)申請,然后根據(jù)當(dāng)前積壓的Task數(shù)量,逐步增長申請的Executor數(shù),如果當(dāng)前有積壓的Task,那么取積壓的Task數(shù)和spark.dynamicAllocation.maxExecutors中的最小值作為Executor數(shù)上限,每次新增加申請的Executor為2的次方,即第一次增加1,第二次增加2,第三次增加4,…。另一方面,如果一個Executor在一段時間內(nèi)都沒有Task運行,則將其回收,但是在Remove Executor時,要保證最少的Executor數(shù),該值通過參數(shù)spark.dynamicAllocation.minExecutors來控制,如果Executor上有Cache的數(shù)據(jù),則永遠不會被Remove,以保證中間數(shù)據(jù)不丟失。
結(jié)語
本文詳細闡述了Spark的任務(wù)調(diào)度,著重討論Spark on Yarn的部署調(diào)度,剖析了從應(yīng)用程序提交到運行的全過程。Spark Schedule算是Spark中的一個大模塊,它負責(zé)任務(wù)下發(fā)與監(jiān)控等,基本上扮演了Spark大腦的角色。了解Spark Schedule有助于幫助我們清楚地認識Spark應(yīng)用程序的運行軌跡,同時在我們實現(xiàn)其他系統(tǒng)時,也可以借鑒Spark的實現(xiàn)。