一、Spark中的基本概念
(1)Application:表示你的應用程序
(2)Driver:表示main()函數,創建SparkContext。由SparkContext負責與ClusterManager通信,進行資源的申請,任務的分配和監控等。程序執行完畢后關閉SparkContext
(3)Executor:某個Application運行在Worker節點上的一個進程,該進程負責運行某些task,並且負責將數據存在內存或者磁盤上。在Spark on Yarn模式下,其進程名稱為 CoarseGrainedExecutor Backend,一個CoarseGrainedExecutor Backend進程有且僅有一個executor對象,它負責將Task包裝成taskRunner,並從線程池中抽取出一個空閑線程運行Task,這樣,每個CoarseGrainedExecutorBackend能並行運行Task的數據就取決於分配給它的CPU的個數。
(4)Worker:集群中可以運行Application代碼的節點。在Standalone模式中指的是通過slave文件配置的worker節點,在Spark on Yarn模式中指的就是NodeManager節點。
(5)Task:在Executor進程中執行任務的工作單元,多個Task組成一個Stage
(6)Job:包含多個Task組成的並行計算,是由Action行為觸發的
(7)Stage:每個Job會被拆分很多組Task,作為一個TaskSet,其名稱為Stage
(8)DAGScheduler:根據Job構建基於Stage的DAG,並提交Stage給TaskScheduler,其划分Stage的依據是RDD之間的依賴關系
(9)TaskScheduler:將TaskSet提交給Worker(集群)運行,每個Executor運行什么Task就是在此處分配的。
二、Spark的運行流程
2.1 Spark的基本運行流程
1、說明
(1)構建Spark Application的運行環境(啟動SparkContext),SparkContext向資源管理器(可以是Standalone、Mesos或YARN)注冊並申請運行Executor資源;
(2)資源管理器分配Executor資源並啟動StandaloneExecutorBackend,Executor運行情況將隨着心跳發送到資源管理器上;
(3)SparkContext構建成DAG圖,將DAG圖分解成Stage,並把Taskset發送給Task Scheduler。Executor向SparkContext申請Task
(4)Task Scheduler將Task發放給Executor運行同時SparkContext將應用程序代碼發放給Executor。
(5)Task在Executor上運行,運行完畢釋放所有資源。
2、圖解
3、Spark運行架構特點
(1)每個Application獲取專屬的executor進程,該進程在Application期間一直駐留,並以多線程方式運行tasks。這種Application隔離機制有其優勢的,無論是從調度角度看(每個Driver調度它自己的任務),還是從運行角度看(來自不同Application的Task運行在不同的JVM中)。當然,這也意味着Spark Application不能跨應用程序共享數據,除非將數據寫入到外部存儲系統。
(2)Spark與資源管理器無關,只要能夠獲取executor進程,並能保持相互通信就可以了。
(3)提交SparkContext的Client應該靠近Worker節點(運行Executor的節點),最好是在同一個Rack里,因為Spark Application運行過程中SparkContext和Executor之間有大量的信息交換;如果想在遠程集群中運行,最好使用RPC將SparkContext提交給集群,不要遠離Worker運行SparkContext。
(4)Task采用了數據本地性和推測執行的優化機制。
4、DAGScheduler
Job=多個stage,Stage=多個同種task, Task分為ShuffleMapTask和ResultTask,Dependency分為ShuffleDependency和NarrowDependency
面向stage的切分,切分依據為寬依賴維護waiting jobs和active jobs,維護waiting stages、active stages和failed stages,以及與jobs的映射關系
主要職能:
1、接收提交Job的主入口,
submitJob(rdd, ...)
或runJob(rdd, ...)
。在SparkContext
里會調用這兩個方法。
- 生成一個Stage並提交,接着判斷Stage是否有父Stage未完成,若有,提交並等待父Stage,以此類推。結果是:DAGScheduler里增加了一些waiting stage和一個running stage。
- running stage提交后,分析stage里Task的類型,生成一個Task描述,即TaskSet。
- 調用
TaskScheduler.submitTask(taskSet, ...)
方法,把Task描述提交給TaskScheduler。TaskScheduler依據資源量和觸發分配條件,會為這個TaskSet分配資源並觸發執行。DAGScheduler
提交job后,異步返回JobWaiter
對象,能夠返回job運行狀態,能夠cancel job,執行成功后會處理並返回結果2、處理
TaskCompletionEvent
- 如果task執行成功,對應的stage里減去這個task,做一些計數工作:
- 如果task是ResultTask,計數器
Accumulator
加一,在job里為該task置true,job finish總數加一。加完后如果finish數目與partition數目相等,說明這個stage完成了,標記stage完成,從running stages里減去這個stage,做一些stage移除的清理工作- 如果task是ShuffleMapTask,計數器
Accumulator
加一,在stage里加上一個output location,里面是一個MapStatus
類。MapStatus
是ShuffleMapTask
執行完成的返回,包含location信息和block size(可以選擇壓縮或未壓縮)。同時檢查該stage完成,向MapOutputTracker
注冊本stage里的shuffleId和location信息。然后檢查stage的output location里是否存在空,若存在空,說明一些task失敗了,整個stage重新提交;否則,繼續從waiting stages里提交下一個需要做的stage- 如果task是重提交,對應的stage里增加這個task
- 如果task是fetch失敗,馬上標記對應的stage完成,從running stages里減去。如果不允許retry,abort整個stage;否則,重新提交整個stage。另外,把這個fetch相關的location和map任務信息,從stage里剔除,從
MapOutputTracker
注銷掉。最后,如果這次fetch的blockManagerId對象不為空,做一次ExecutorLost
處理,下次shuffle會換在另一個executor上去執行。- 其他task狀態會由
TaskScheduler
處理,如Exception, TaskResultLost, commitDenied等。3、其他與job相關的操作還包括:cancel job, cancel stage, resubmit failed stage等
其他職能:
cacheLocations 和 preferLocation
5、TaskScheduler
維護task和executor對應關系,executor和物理資源對應關系,在排隊的task和正在跑的task。
內部維護一個任務隊列,根據FIFO或Fair策略,調度任務。
TaskScheduler
本身是個接口,spark里只實現了一個TaskSchedulerImpl
,理論上任務調度可以定制。
主要功能:
1、submitTasks(taskSet)
,接收DAGScheduler
提交來的tasks
- 為tasks創建一個
TaskSetManager
,添加到任務隊列里。TaskSetManager
跟蹤每個task的執行狀況,維護了task的許多具體信息。- 觸發一次資源的索要。
- 首先,
TaskScheduler
對照手頭的可用資源和Task隊列,進行executor分配(考慮優先級、本地化等策略),符合條件的executor會被分配給TaskSetManager
。- 然后,得到的Task描述交給
SchedulerBackend
,調用launchTask(tasks)
,觸發executor上task的執行。task描述被序列化后發給executor,executor提取task信息,調用task的run()
方法執行計算。
2、cancelTasks(stageId)
,取消一個stage的tasks
- 調用
SchedulerBackend
的killTask(taskId, executorId, ...)
方法。taskId和executorId在TaskScheduler
里一直維護着。
3、resourceOffer(offers: Seq[Workers])
,這是非常重要的一個方法,調用者是SchedulerBacnend
,用途是底層資源SchedulerBackend
把空余的workers資源交給TaskScheduler
,讓其根據調度策略為排隊的任務分配合理的cpu和內存資源,然后把任務描述列表傳回給SchedulerBackend
- 從worker offers里,搜集executor和host的對應關系、active executors、機架信息等等
- worker offers資源列表進行隨機洗牌,任務隊列里的任務列表依據調度策略進行一次排序
- 遍歷每個taskSet,按照進程本地化、worker本地化、機器本地化、機架本地化的優先級順序,為每個taskSet提供可用的cpu核數,看是否滿足
- 默認一個task需要一個cpu,設置參數為
"spark.task.cpus=1"
- 為taskSet分配資源,校驗是否滿足的邏輯,最終在
TaskSetManager
的resourceOffer(execId, host, maxLocality)
方法里- 滿足的話,會生成最終的任務描述,並且調用
DAGScheduler
的taskStarted(task, info)
方法,通知DAGScheduler
,這時候每次會觸發DAGScheduler
做一次submitMissingStage
的嘗試,即stage的tasks都分配到了資源的話,馬上會被提交執行
4、statusUpdate(taskId, taskState, data)
,另一個非常重要的方法,調用者是SchedulerBacnend
,用途是SchedulerBacnend
會將task執行的狀態匯報給TaskScheduler
做一些決定
- 若
TaskLost
,找到該task對應的executor,從active executor里移除,避免這個executor被分配到其他task繼續失敗下去。- task finish包括四種狀態:finished, killed, failed, lost。只有finished是成功執行完成了。其他三種是失敗。
- task成功執行完,調用
TaskResultGetter.enqueueSuccessfulTask(taskSet, tid, data)
,否則調用TaskResultGetter.enqueueFailedTask(taskSet, tid, state, data)
。TaskResultGetter
內部維護了一個線程池,負責異步fetch task執行結果並反序列化。默認開四個線程做這件事,可配參數"spark.resultGetter.threads"=4
。
TaskResultGetter取task result的邏輯
1、對於success task,如果taskResult里的數據是直接結果數據,直接把data反序列出來得到結果;如果不是,會調用
blockManager.getRemoteBytes(blockId)
從遠程獲取。如果遠程取回的數據是空的,那么會調用TaskScheduler.handleFailedTask
,告訴它這個任務是完成了的但是數據是丟失的。否則,取到數據之后會通知BlockManagerMaster
移除這個block信息,調用TaskScheduler.handleSuccessfulTask
,告訴它這個任務是執行成功的,並且把result data傳回去。2、對於failed task,從data里解析出fail的理由,調用
TaskScheduler.handleFailedTask
,告訴它這個任務失敗了,理由是什么。
6、SchedulerBackend
在TaskScheduler
下層,用於對接不同的資源管理系統,SchedulerBackend
是個接口,需要實現的主要方法如下:
def start(): Unit def stop(): Unit def reviveOffers(): Unit // 重要方法:SchedulerBackend把自己手頭上的可用資源交給TaskScheduler,TaskScheduler根據調度策略分配給排隊的任務嗎,返回一批可執行的任務描述,SchedulerBackend負責launchTask,即最終把task塞到了executor模型上,executor里的線程池會執行task的run() def killTask(taskId: Long, executorId: String, interruptThread: Boolean): Unit = throw new UnsupportedOperationException
粗粒度:進程常駐的模式,典型代表是standalone模式,mesos粗粒度模式,yarn
細粒度:mesos細粒度模式
這里討論粗粒度模式,更好理解:CoarseGrainedSchedulerBackend
。
維護executor相關信息(包括executor的地址、通信端口、host、總核數,剩余核數),手頭上executor有多少被注冊使用了,有多少剩余,總共還有多少核是空的等等。
主要職能
1、Driver端主要通過actor監聽和處理下面這些事件:
RegisterExecutor(executorId, hostPort, cores, logUrls)
。這是executor添加的來源,通常worker拉起、重啟會觸發executor的注冊。CoarseGrainedSchedulerBackend
把這些executor維護起來,更新內部的資源信息,比如總核數增加。最后調用一次makeOffer()
,即把手頭資源丟給TaskScheduler
去分配一次,返回任務描述回來,把任務launch起來。這個makeOffer()
的調用會出現在任何與資源變化相關的事件中,下面會看到。StatusUpdate(executorId, taskId, state, data)
。task的狀態回調。首先,調用TaskScheduler.statusUpdate
上報上去。然后,判斷這個task是否執行結束了,結束了的話把executor上的freeCore加回去,調用一次makeOffer()
。ReviveOffers
。這個事件就是別人直接向SchedulerBackend
請求資源,直接調用makeOffer()
。KillTask(taskId, executorId, interruptThread)
。這個killTask的事件,會被發送給executor的actor,executor會處理KillTask
這個事件。StopExecutors
。通知每一個executor,處理StopExecutor
事件。RemoveExecutor(executorId, reason)
。從維護信息中,那這堆executor涉及的資源數減掉,然后調用TaskScheduler.executorLost()
方法,通知上層我這邊有一批資源不能用了,你處理下吧。TaskScheduler
會繼續把executorLost
的事件上報給DAGScheduler
,原因是DAGScheduler
關心shuffle任務的output location。DAGScheduler
會告訴BlockManager
這個executor不可用了,移走它,然后把所有的stage的shuffleOutput信息都遍歷一遍,移走這個executor,並且把更新后的shuffleOutput信息注冊到MapOutputTracker
上,最后清理下本地的CachedLocations
Map。
2、reviveOffers()
方法的實現。直接調用了makeOffers()
方法,得到一批可執行的任務描述,調用launchTasks
。
3、launchTasks(tasks: Seq[Seq[TaskDescription]])
方法。
- 遍歷每個task描述,序列化成二進制,然后發送給每個對應的executor這個任務信息
- 如果這個二進制信息太大,超過了9.2M(默認的akkaFrameSize 10M 減去 默認 為akka留空的200K),會出錯,abort整個taskSet,並打印提醒增大akka frame size
- 如果二進制數據大小可接受,發送給executor的actor,處理
LaunchTask(serializedTask)
事件。
7、Executor
Executor是spark里的進程模型,可以套用到不同的資源管理系統上,與SchedulerBackend
配合使用。內部有個線程池,running tasks map,以及actor,接收上面提到的由SchedulerBackend
發來的事件。
事件處理
launchTask
。根據task描述,生成一個TaskRunner
線程,丟盡running tasks map里,用線程池執行這個TaskRunner
killTask
。從running tasks map里拿出線程對象,調它的kill方法。
三、Spark在不同集群中的運行架構
Spark注重建立良好的生態系統,它不僅支持多種外部文件存儲系統,提供了多種多樣的集群運行模式。部署在單台機器上時,既可以用本地(Local)模式運行,也可以使用偽分布式模式來運行;當以分布式集群部署的時候,可以根據自己集群的實際情況選擇Standalone模式(Spark自帶的模式)、YARN-Client模式或者YARN-Cluster模式。Spark的各種運行模式雖然在啟動方式、運行位置、調度策略上各有不同,但它們的目的基本都是一致的,就是在合適的位置安全可靠的根據用戶的配置和Job的需要運行和管理Task。
3.1 Spark on Standalone運行過程
Standalone模式是Spark實現的資源調度框架,其主要的節點有Client節點、Master節點和Worker節點。其中Driver既可以運行在Master節點上中,也可以運行在本地Client端。當用spark-shell交互式工具提交Spark的Job時,Driver在Master節點上運行;當使用spark-submit工具提交Job或者在Eclips、IDEA等開發平台上使用”new SparkConf().setMaster(“spark://master:7077”)”方式運行Spark任務時,Driver是運行在本地Client端上的。
運行過程文字說明
1、我們提交一個任務,任務就叫Application
2、初始化程序的入口SparkContext,
2.1 初始化DAG Scheduler
2.2 初始化Task Scheduler
3、Task Scheduler向master去進行注冊並申請資源(CPU Core和Memory)
4、Master根據SparkContext的資源申請要求和Worker心跳周期內報告的信息決定在哪個Worker上分配資源,然后在該Worker上獲取資源,然后啟動StandaloneExecutorBackend;順便初
始化好了一個線程池
5、StandaloneExecutorBackend向Driver(SparkContext)注冊,這樣Driver就知道哪些Executor為他進行服務了。
到這個時候其實我們的初始化過程基本完成了,我們開始執行transformation的代碼,但是代碼並不會真正的運行,直到我們遇到一個action操作。生產一個job任務,進行stage的划分
6、SparkContext將Applicaiton代碼發送給StandaloneExecutorBackend;並且SparkContext解析Applicaiton代碼,構建DAG圖,並提交給DAG Scheduler分解成Stage(當碰到Action操作 時,就會催生Job;每個Job中含有1個或多個Stage,Stage一般在獲取外部數據和shuffle之前產生)。
7、將Stage(或者稱為TaskSet)提交給Task Scheduler。Task Scheduler負責將Task分配到相應的Worker,最后提交給StandaloneExecutorBackend執行;
8、對task進行序列化,並根據task的分配算法,分配task
9、對接收過來的task進行反序列化,把task封裝成一個線程
10、開始執行Task,並向SparkContext報告,直至Task完成。
11、資源注銷
運行過程圖形說明
3.2 Spark on YARN運行過程
YARN是一種統一資源管理機制,在其上面可以運行多套計算框架。目前的大數據技術世界,大多數公司除了使用Spark來進行數據計算,由於歷史原因或者單方面業務處理的性能考慮而使用着其他的計算框架,比如MapReduce、Storm等計算框架。Spark基於此種情況開發了Spark on YARN的運行模式,由於借助了YARN良好的彈性資源管理機制,不僅部署Application更加方便,而且用戶在YARN集群中運行的服務和Application的資源也完全隔離,更具實踐應用價值的是YARN可以通過隊列的方式,管理同時運行在集群中的多個服務。
Spark on YARN模式根據Driver在集群中的位置分為兩種模式:一種是YARN-Client模式,另一種是YARN-Cluster(或稱為YARN-Standalone模式)。
3.2.1 YARN框架流程
任何框架與YARN的結合,都必須遵循YARN的開發模式。在分析Spark on YARN的實現細節之前,有必要先分析一下YARN框架的一些基本原理。
3.2.2 YARN-Client
Yarn-Client模式中,Driver在客戶端本地運行,這種模式可以使得Spark Application和客戶端進行交互,因為Driver在客戶端,所以可以通過webUI訪問Driver的狀態,默認是http://xxx:4040訪問,而YARN通過http:// xxx:8088訪問。
YARN-client的工作流程分為以下幾個步驟:
文字說明
1.Spark Yarn Client向YARN的ResourceManager申請啟動Application Master。同時在SparkContent初始化中將創建DAGScheduler和TASKScheduler等,由於我們選擇的是Yarn-Client模式,程序會選擇YarnClientClusterScheduler和YarnClientSchedulerBackend;
2.ResourceManager收到請求后,在集群中選擇一個NodeManager,為該應用程序分配第一個Container,要求它在這個Container中啟動應用程序的ApplicationMaster,與YARN-Cluster區別的是在該ApplicationMaster不運行SparkContext,只與SparkContext進行聯系進行資源的分派;
3.Client中的SparkContext初始化完畢后,與ApplicationMaster建立通訊,向ResourceManager注冊,根據任務信息向ResourceManager申請資源(Container);
4.一旦ApplicationMaster申請到資源(也就是Container)后,便與對應的NodeManager通信,要求它在獲得的Container中啟動啟動CoarseGrainedExecutorBackend,CoarseGrainedExecutorBackend啟動后會向Client中的SparkContext注冊並申請Task;
5.Client中的SparkContext分配Task給CoarseGrainedExecutorBackend執行,CoarseGrainedExecutorBackend運行Task並向Driver匯報運行的狀態和進度,以讓Client隨時掌握各個任務的運行狀態,從而可以在任務失敗時重新啟動任務;
6.應用程序運行完成后,Client的SparkContext向ResourceManager申請注銷並關閉自己。
圖片說明
3.2.3 YARN-Cluster
在YARN-Cluster模式中,當用戶向YARN中提交一個應用程序后,YARN將分兩個階段運行該應用程序:第一個階段是把Spark的Driver作為一個ApplicationMaster在YARN集群中先啟動;第二個階段是由ApplicationMaster創建應用程序,然后為它向ResourceManager申請資源,並啟動Executor來運行Task,同時監控它的整個運行過程,直到運行完成。
YARN-cluster的工作流程分為以下幾個步驟:
文字說明
1. Spark Yarn Client向YARN中提交應用程序,包括ApplicationMaster程序、啟動ApplicationMaster的命令、需要在Executor中運行的程序等;
2. ResourceManager收到請求后,在集群中選擇一個NodeManager,為該應用程序分配第一個Container,要求它在這個Container中啟動應用程序的ApplicationMaster,其中ApplicationMaster進行SparkContext等的初始化;
3. ApplicationMaster向ResourceManager注冊,這樣用戶可以直接通過ResourceManage查看應用程序的運行狀態,然后它將采用輪詢的方式通過RPC協議為各個任務申請資源,並監控它們的運行狀態直到運行結束;
4. 一旦ApplicationMaster申請到資源(也就是Container)后,便與對應的NodeManager通信,要求它在獲得的Container中啟動啟動CoarseGrainedExecutorBackend,CoarseGrainedExecutorBackend啟動后會向ApplicationMaster中的SparkContext注冊並申請Task。這一點和Standalone模式一樣,只不過SparkContext在Spark Application中初始化時,使用CoarseGrainedSchedulerBackend配合YarnClusterScheduler進行任務的調度,其中YarnClusterScheduler只是對TaskSchedulerImpl的一個簡單包裝,增加了對Executor的等待邏輯等;
5. ApplicationMaster中的SparkContext分配Task給CoarseGrainedExecutorBackend執行,CoarseGrainedExecutorBackend運行Task並向ApplicationMaster匯報運行的狀態和進度,以讓ApplicationMaster隨時掌握各個任務的運行狀態,從而可以在任務失敗時重新啟動任務;
6. 應用程序運行完成后,ApplicationMaster向ResourceManager申請注銷並關閉自己。
圖片說明
3.2.4 YARN-Client 與 YARN-Cluster 區別
理解YARN-Client和YARN-Cluster深層次的區別之前先清楚一個概念:Application Master。在YARN中,每個Application實例都有一個ApplicationMaster進程,它是Application啟動的第一個容器。它負責和ResourceManager打交道並請求資源,獲取資源之后告訴NodeManager為其啟動Container。從深層次的含義講YARN-Cluster和YARN-Client模式的區別其實就是ApplicationMaster進程的區別。
1、YARN-Cluster模式下,Driver運行在AM(Application Master)中,它負責向YARN申請資源,並監督作業的運行狀況。當用戶提交了作業之后,就可以關掉Client,作業會繼續在YARN上運行,因而YARN-Cluster模式不適合運行交互類型的作業;
2、YARN-Client模式下,Application Master僅僅向YARN請求Executor,Client會和請求的Container通信來調度他們工作,也就是說Client不能離開。