1.集羣管理腳本
start-all.sh
調用 start-master.sh CLASS=”org.spark.deploy.master.Master”
執行main(),並傳入一些參數。
調用 start-slave.sh CLASS=”org.spark.deploy.worker.Worker”
執行main(),並傳入一些參數。
stop-all.sh 類似。
2.任務提交腳本
spark-submit.sh
spark-class.sh org.apache.spark.deploy.SparkSubmit
執行該類的main,傳入jar包相關參數。
spark-shell.sh
調用 spark-submit.sh
spark-class.sh org.apache.spark.deploy.SparkSubmit
傳入的啓動類 org.apache.spark.repl.Main
3.StandAlone集羣的啓動
- Master啓動時首先創一個RpcEnv對象,負責管理所有通信邏輯。
- Master通過RpcEnv對象創建一個Endpoint,Master就是一個Endpoint,Worker可以與其進行通信。
- Worker啓動時也是創一個RpcEnv對象。
- Worker通過RpcEnv對象創建一個Endpoint。
- Worker通過RpcEnv對象,建立到Master的連接,獲取到一個RpcEndpointRef對象,通過該對象可以與Master通信。
- Worker向Master註冊,註冊內容包括主機名、端口、CPU Core數量、內存數量。
- Master接收到Worker的註冊,將註冊信息維護在內存中的Table中,其中還包含了一個到Worker的RpcEndpointRef對象引用。
- Master回覆Worker已經接收到註冊,告知Worker已經註冊成功。
- 此時如果有用戶提交Spark程序,Master需要協調啓動Driver;而Worker端接收到成功註冊響應後,開始週期性向Master發送心跳。
4.應用提交與運行過程
上圖中,通過不同顏色或類型的線條,給出瞭如下6個核心的交互流程,我們會詳細說明:
橙色:提交用戶Spark程序
用戶提交一個Spark程序,主要的流程如下所示:
- 用戶執行spark-submit腳本提交一個Spark程序,會創建一個ClientEndpoint對象,該對象負責與Master通信交互。
- ClientEndpoint向Master發送一個RequestSubmitDriver消息,表示提交用戶程序。
- Master收到RequestSubmitDriver消息,向ClientEndpoint回覆。SubmitDriverResponse,表示用戶程序已經完成註冊。
- ClientEndpoint向Master發送RequestDriverStatus消息,請求Driver狀態
- 如果當前用戶程序對應的Driver已經啓動,則ClientEndpoint直接退出,完成提交用戶程序。
紫色:啓動Driver進程
當用戶提交用戶Spark程序後,需要啓動Driver來處理用戶程序的計算邏輯,完成計算任務,這時Master協調需要啓動一個Driver,具體流程如下所示:
- Maser內存中維護着用戶提交計算的任務Application,每次內存結構變更都會觸發調度,向Worker發送LaunchDriver請求
- Worker收到LaunchDriver消息,會啓動一個DriverRunner線程去執行LaunchDriver的任務
- DriverRunner線程在Worker上啓動一個新的JVM實例,該JVM實例內運行一個Driver進程,該Driver會創建SparkContext對象
紅色:註冊Application
Dirver啓動以後,它會創建SparkContext對象,初始化計算過程中必需的基本組件,並向Master註冊Application,流程描述如下:
- 創建SparkEnv對象,創建並管理一些基本組件
- 創建TaskScheduler,負責Task調度
- 創建StandaloneSchedulerBackend,負責與ClusterManager進行資源協商
- 創建DriverEndpoint,其它組件可以與Driver進行通信
- 在StandaloneSchedulerBackend內部創建一個StandaloneAppClient,負責處理與Master的通信交互
- StandaloneAppClient創建一個ClientEndpoint,實際負責與Master通信
- ClientEndpoint向Master發送RegisterApplication消息,註冊Application
- Master收到RegisterApplication請求後,回覆ClientEndpoint一個RegisteredApplication消息,表示已經註冊成功
藍色:啓動Executor進程
- Master向Worker發送LaunchExecutor消息,請求啓動Executor;同時Master會向Driver發送ExecutorAdded消息,表示Master已經新增了一個Executor(此時還未啓動)
- Worker收到LaunchExecutor消息,會啓動一個ExecutorRunner線程去執行LaunchExecutor的任務
- Worker向Master發送ExecutorStageChanged消息,通知Executor狀態已發生變化
- Master向Driver發送ExecutorUpdated消息,此時Executor已經啓動
粉色:啓動Task執行
- StandaloneSchedulerBackend啓動一個DriverEndpoint
- DriverEndpoint啓動後,會週期性地檢查Driver維護的Executor的狀態,如果有空閒的Executor便會調度任務執行
- DriverEndpoint向TaskScheduler發送Resource Offer請求
- 如果有可用資源啓動Task,則DriverEndpoint向Executor發送LaunchTask請求
- Executor進程內部的CoarseGrainedExecutorBackend調用內部的Executor線程的launchTask方法啓動Task
- Executor線程內部維護一個線程池,創建一個TaskRunner線程並提交到線程池執行
綠色:Task運行完成
- Executor進程內部的Executor線程通知CoarseGrainedExecutorBackend,Task運行完成
- CoarseGrainedExecutorBackend向DriverEndpoint發送StatusUpdated消息,通知Driver運行的Task狀態發生變更
- StandaloneSchedulerBackend調用TaskScheduler的updateStatus方法更新Task狀態
- StandaloneSchedulerBackend繼續調用TaskScheduler的resourceOffers方法,調度其他任務運行
5.spark任務的劃分
1、Application
用戶編寫的Spark程序,完成一個計算任務的處理。它是由一個Driver程序和一組運行於Spark集羣上的Executor組成。
2、Job
用戶程序中,每次調用Action時,邏輯上會生成一個Job,一個Job包含了多個Stage。
3、Stage
Stage包括兩類:ShuffleMapStage和ResultStage,如果用戶程序中調用了需要進行Shuffle計算的Operator,如groupByKey等,就會以Shuffle爲邊界分成ShuffleMapStage和ResultStage。
4、TaskSet
基於Stage可以直接映射爲TaskSet,一個TaskSet封裝了一次需要運算的、具有相同處理邏輯的Task,這些Task可以並行計算,粗粒度的調度是以TaskSet爲單位的。
5、Task
Task是在物理節點上運行的基本單位,Task包含兩類:ShuffleMapTask和ResultTask,分別對應於Stage中ShuffleMapStage和ResultStage中的一個執行基本單元。
6.Shuffle過程
1.mapreduce-shuffle
Shuffle橫跨Map端和Reduce端,在Map端包括Spill、Merge過程,在Reduce端包括copy和sort、reduce過程。
Map任務會不斷的以(k,v)形式將數據寫入到環形緩衝區kvbuffer,在寫的過程中,同時記錄kvmeta,寫入索引信息。
kvmeta的記錄和(k,v)數據的記錄,是從kvbuffer中對着頭寫,一般當寫道剩餘空間還有20%的時候啓動sortAndSpillt線程。這個時候要注意,如果Map再有數據寫入,kvmeta和(k,v)的寫入起始位置會發生改變。split線程啓動後,會根據partition和key在kvmeta裏面排好序。然後線程啓動寫文件過程,產生一個索引文件(如果內存不足)和一個數據文件。
reduce端就會從各個map的httpserver上考取自己的partition數據,拷貝回來的數據都是有序的,多個map相同partition的數據進行排序,一邊排序一邊進行數據的合併,最後合併成一個大文件。
2.Spark-HashShufle
Map任務會爲每個Reduce創建對應的bucket,Map產生的結果會根據設置的Partitioner得到對應的bucketId,然後填充到相應的bucket中去。每個Map的輸出結果可能包含所有的Reduce所需要的數據,所以每個Map會創建R個bucket(R是reduce的個數),M個Map總共會創建M*R個bucket。 然後bucket緩衝區中的數據就寫入到以文件名hash值爲名的blockfile中,Reduce從遠端或者本地逐個讀取自己需要的文件,放到一個大的Hashmap中,key是Map輸出的key,Map輸出對應這個key的所有value組成HashMap的value,如果內存不夠就溢寫到磁盤。 缺點:大量小文件的讀寫問題。
SPARK的Shuffle過程 – HashShuffle改進:
這種方式的改進是減少了map端的磁盤文件數量,map在某個節點上第一次執行,爲每個reduce創建bucket對應的輸出文件,把這些文件組織成ShuffleFileGroup,當這次map執行完之後,這個ShuffleFileGroup可以釋放爲下次循環利用;當又有map在這個節點上執行時,不需要創建新的bucket文件,而是在上次的ShuffleFileGroup中取得已經創建的文件繼續追加寫一個segment;當前次map還沒執行完,ShuffleFileGroup還沒有釋放,這時如果有新的map在這個節點上執行,無法循環利用這個ShuffleFileGroup,而是隻能創建新的bucket文件組成新的ShuffleFileGroup來寫輸出。reduce過程與第一階段相同。
3. spark Shuffle-SortShuffle
實現邏輯類似於Hadoop MapReduce,Hash Shuffle每一個reducers產生一個文件,但是Sort Shuffle只是產生一個按照reducer id排序可索引的文件,也就是一個數據文件和一個索引文件。
數據文件中的數據按照Reducer排序,但屬於同一個Reducer的數據不排序。Mapper產生的數據先放到AppendOnlyMap這個數據結構中,如果內存不夠,數據則會spill到磁盤,最後合併成一個文件。reduce端進行遠程或者本地讀取進行合併。
需要注意的一點是對於reducer數比較少的情況,Hash Shuffle明顯要比Sort Shuffle快,因此Sort Shuffle有個“fallback”計劃,對於reducers數少於 “spark.shuffle.sort.bypassMergeThreshold” (200 by default),我們使用fallback計劃,hashing相關數據到分開的文件,然後合併這些文件爲一個。
7.內存管理
堆內內存:Executor 的內存管理建立在 JVM 的內存管理之上。–executor-memory 指定Executor內存。並不能準確記錄實際可用的堆內內存。
堆外內存:Spark 引入了堆外(Off-heap)內存,利用 JDK Unsafe API進行實現。存儲序列化後的二進制數據。通過配置 spark.memory.offHeap.enabled 參數啓用,並由 spark.memory.offHeap.size 參數設定堆外空間的大小
1.堆內內存-靜態內存
2. 堆內內存-統一內存
3.堆外內存-靜態
4.堆外內存-統一
5.動態佔用
設定基本的存儲內存和執行內存區域(spark.storage.storageFraction 參數),該設定確定了雙方各自擁有的空間的範圍。 雙方的空間都不足時,則存儲到硬盤;若己方空間不足而對方空餘時,可借用對方的空間;(存儲空間不足是指不足以放下一個完整的 Block)。執行內存的空間被對方佔用後,可讓對方將佔用的部分轉存到硬盤,然後"歸還"借用的空間。
存儲內存的空間被對方佔用後,無法讓對方“歸還”,只有等待結束。