spark內核解析

1、spark部署模式

Spark的運行模式取決於傳遞給SparkContext的MASTER環境變量的值,目前包括:

本地運行:local、local[K]、local[*]

Standalone模式運行:spark://HOST:PORT

Mesos集羣上運行:mesos://HOST:PORT

Yarn集羣上運行:

yarn-client:Driver進程在本地,Executor進程在Yarn集羣上,部署模式必須使用固定值:--deploy-mode client。

yarn-cluster:Driver進程在Yarn集羣上,Work進程也在Yarn集羣上,部署模式必須使用固定值:--deploy-mode cluster。

–master MASTER_URL :決定了Spark任務提交給哪種集羣處理。

–deploy-mode DEPLOY_MODE:決定了Driver的運行方式,可選值爲Client或者Cluster。

2、YARN Cluster模式

在YARN Cluster模式下,任務提交後會和ResourceManager通訊申請啓動ApplicationMaster,隨後ResourceManager分配container,在合適的NodeManager上啓動ApplicationMaster,此時的ApplicationMaster就是Driver。

Driver啓動後向ResourceManager申請Executor內存,ResourceManager接到ApplicationMaster的資源申請後會分配container,然後在合適的NodeManager上啓動Executor進程,Executor進程啓動後會向Driver反向註冊,Executor全部註冊完成後Driver開始執行main函數。之後執行到Action算子時,觸發一個job,並根據寬依賴開始劃分stage,每個stage生成對應的taskSet,之後將task分發到各個Executor上執行。

3、Spark 通訊架構

Spark2.x版本使用Netty通訊框架作爲內部通訊組件。spark 基於netty新的rpc框架借鑑了Akka的中的設計,它是基於Actor模型。

4、Spark 任務調度機制

在工廠環境下,Spark集羣的部署方式一般爲YARN-Cluster模式,之後的內核分析內容中我們默認集羣的部署方式爲YARN-Cluster模式。

1)Spark任務調度概述

一個Spark應用程序包括Job、Stage以及Task三個概念:

Job是以Action方法爲界,遇到一個Action方法則觸發一個Job;

Stage是Job的子集,以RDD寬依賴(即Shuffle)爲界,遇到Shuffle做一次劃分;

Task是Stage的子集,以並行度(分區數)來衡量,分區數是多少,則有多少個task;

Spark的任務調度總體來說分兩路進行,一路是Stage級的調度,一路是Task級的調度

Spark RDD通過其Transactions操作,形成了RDD血緣關係圖,即DAG,最後通過Action的調用,觸發Job並調度執行。DAGScheduler負責Stage級的調度,主要是將job切分成若干Stages,並將每個Stage打包成TaskSet交給TaskScheduler調度。TaskScheduler負責Task級的調度,調度過程中SchedulerBackend負責提供可用資源。

Driver初始化SparkContext過程中,會分別初始化DAGScheduler、TaskScheduler、SchedulerBackend以及HeartbeatReceiver。

2)Spark Stage級調度

Spark的任務調度是從DAG切割開始,主要是由DAGScheduler來完成。當遇到一個Action操作後就會觸發一個Job的計算,並交給DAGScheduler來提交。

Job由最終的RDD和Action方法封裝而成,SparkContext將Job交給DAGScheduler提交,它會根據RDD的血緣關係構成的DAG進行切分,將一個Job劃分爲若干Stages,具體劃分策略是,由最終的RDD不斷通過依賴回溯判斷父依賴是否是寬依賴,即以Shuffle爲界,劃分Stage,窄依賴的RDD之間被劃分到同一個Stage中。

劃分的Stages分兩類,一類叫做ResultStage,爲DAG最下游的Stage,由Action方法決定,另一類叫做ShuffleMapStage,爲下游Stage準備數據。

實際執行的時候,數據記錄會一氣呵成地執行RDD-0到RDD-2的轉化。

一個Stage是否被提交,需要判斷它的父Stage是否執行,只有在父Stage執行完畢才能提交當前Stage,如果一個Stage沒有父Stage,那麼從該Stage開始提交。

3)Spark Task級調度

Spark Task的調度是由TaskScheduler來完成,由前文可知,DAGScheduler將Stage打包到TaskSet交給TaskScheduler,TaskScheduler會將TaskSet封裝爲TaskSetManager加入到調度隊列中,TaskSetManager結構如下圖所示。

TaskSetManager負責監控管理同一個Stage中的Tasks,TaskScheduler就是以TaskSetManager爲單元來調度任務。

TaskScheduler在SchedulerBackend“問”它的時候,會從調度隊列中按照指定的調度策略選擇TaskSetManager去調度運行

調度策略

TaskScheduler會先把DAGScheduler給過來的TaskSet封裝成TaskSetManager扔到任務隊列裏,然後再從任務隊列裏按照一定的規則把它們取出來在SchedulerBackend給過來的Executor上運行

TaskScheduler支持兩種調度策略,一種是FIFO,也是默認的調度策略,另一種是FAIR

從調度隊列中拿到TaskSetManager後,那麼接下來的工作就是TaskSetManager按照一定的規則一個個取出task給TaskScheduler,TaskScheduler再交給SchedulerBackend去發到Executor上執行。前面也提到,TaskSetManager封裝了一個Stage的所有task,並負責管理調度這些task。

在調度執行時,Spark調度總是會盡量讓每個task以最高的本地性級別來啓動。

5、Spark Shuffle解析

在劃分stage時,最後一個stage稱爲finalStage,它本質上是一個ResultStage對象,前面的所有stage被稱爲ShuffleMapStage。

ShuffleMapStage的結束伴隨着shuffle文件的寫磁盤

1)Shuffle中的任務個數

Spark Shuffle分爲map階段和reduce階段,或者稱之爲ShuffleRead階段和ShuffleWrite階段,那麼對於一次Shuffle,map過程和reduce過程都會由若干個task來執行,當執行到Shuffle操作時,map端的task個數和partition個數一致。reduce端的stage默認取spark.default.parallelism這個配置項的值作爲分區數,如果沒有配置,則以map端的最後一個RDD的分區數作爲其分區數(也就是N),那麼分區數就決定了reduce端的task的個數。

HashShuffle解析

未經優化的:下一個stage的task有多少個,當前stage的每個task就要創建多少份磁盤文件

優化的:下一批task就會複用之前已有的shuffleFileGroup,包括其中的磁盤文件,也就是說,此時task會將數據寫入已有的磁盤文件中,而不會寫入新的磁盤文件中

SortShuffle解析

SortShuffleManager的運行機制主要分成兩種,一種是普通運行機制,另一種是bypass運行機制。當shuffle read task的數量小於等於spark.shuffle.sort. bypassMergeThreshold參數的值時(默認爲200),就會啓用bypass機制。

普通運行機制

由於一個task就只對應一個磁盤文件,也就意味着該task爲下游stage的task準備的數據都在這一個文件中,因此還會單獨寫一份索引文件,其中標識了下游各個task的數據在文件中的start offset與end offset。

bypass運行機制

bypass運行機制的觸發條件如下:

1)shuffle map task數量小於spark.shuffle.sort.bypassMergeThreshold參數的值。

2)不是聚合類的shuffle算子。

而該機制與普通SortShuffleManager運行機制的不同在於:第一,磁盤寫機制不同;第二,不會進行排序。也就是說,啓用該機制的最大好處在於,shuffle write過程中,不需要進行數據的排序操作,也就節省掉了這部分的性能開銷。

6、Spark 內存管理

1)堆內內存

堆內內存的大小,由 Spark 應用程序啓動時的 –executor-memory 或 spark.executor.memory 參數配置,緩存 RDD 數據和廣播(Broadcast)數據時佔用的內存被規劃爲存儲(Storage)內存,而這些任務在執行 Shuffle 時佔用的內存被規劃爲執行(Execution)內存,剩餘的部分不做特殊規劃,那些 Spark 內部的對象實例,或者用戶定義的 Spark 應用程序中的對象實例,均佔用剩餘的空間。

有時Spark 並不能準確記錄實際可用的堆內內存,從而也就無法完全避免內存溢出(OOM, Out of Memory)的異常。

2)堆外內存

可以直接在工作節點的系統內存中開闢空間,存儲經過序列化的二進制數據。堆外內存之所以能夠被精確的申請和釋放,是由於內存的申請和釋放不再通過JVM機制,而是直接向操作系統申請。

內存空間的分配

1)靜態內存管理

存儲內存、執行內存和其他內存的大小在 Spark 應用程序運行期間均爲固定的,但用戶可以應用程序啓動前進行配置

2)統一內存管理

存儲內存和執行內存共享同一塊空間,可以動態佔用對方的空閒區域

雙方的空間都不足時,則存儲到硬盤;若己方空間不足而對方空餘時,可借用對方的空間;

存儲內存管理

憑藉血統,Spark 保證了每一個 RDD 都可以被重新恢復。

如果一個 RDD 上要執行多次行動,可以在第一次行動中使用 persist 或 cache 方法,在內存或磁盤中持久化或緩存這個 RDD,從而在後面的行動時提升計算速度。

cache 方法是使用默認的 MEMORY_ONLY 的存儲級別將 RDD 持久化到內存,故緩存是一種特殊的持久化

默認的存儲級別都是僅在內存存儲一份

在對 RDD 持久化時,Spark 規定了 MEMORY_ONLY、MEMORY_AND_DISK 等 7 種不同的存儲級別

MEMORY_ONLY:以非序列化的Java對象的方式持久化在JVM內存中

MEMORY_AND_DISK:同上,但是當某些partition無法存儲在內存中時,會持久化到磁盤中。

MEMORY_ONLY_SER:同MEMORY_ONLY,但是會使用Java序列化方式,將Java對象序列化後進行持久化。

MEMORY_AND_DISK_SER:同MEMORY_AND_DISK,但是使用序列化方式持久化Java對象

DISK_ONLY:使用非序列化Java對象的方式持久化,完全存儲到磁盤上

MEMORY_ONLY_2、MEMORY_AND_DISK_2:如果是尾部加了2的持久化級別,表示將持久化數據複用一份

而存儲級別是以下 5 個變量的組合:

class StorageLevel private(

private var _useDisk: Boolean, //磁盤

private var _useMemory: Boolean, //這裏其實是指堆內內存

private var _useOffHeap: Boolean, //堆外內存

private var _deserialized: Boolean, //是否爲非序列化

private var _replication: Int = 1 //副本個數

)

RDD的緩存過程

RDD 在緩存到存儲內存之後,Partition 被轉換成 Block

執行內存管理

執行內存主要用來存儲任務在執行 Shuffle 時佔用的內存,Shuffle 是按照一定規則對 RDD 數據重新分區的過程,主要是對Shuffle 的 Write 和 Read 兩階段分析

6、核心組件解析

每個節點上都有一個BlockManager,BlockManager中有3個非常重要的組件:

  • DiskStore:負責對磁盤數據進行讀寫;
  • MemoryStore:負責對內存數據進行讀寫;
  • BlockTransferService:負責建立BlockManager到遠程其他節點的BlockManager的連接,負責對遠程其他節點的BlockManager的數據進行讀寫

共享變量

一種是Broadcast Variable廣播變量),另一種是Accumulator累加變量

Broadcast Variable是只讀的,並且在每個Executor上只會有一個副本,而不會爲每個task都拷貝一份副本。

Accumulator是僅僅被相關操作累加的變量,因此可以在並行中被有效地支持。它們可用於實現計數器(如MapReduce)或總和計數。Accumulator存在於Driver端,從節點讀取不到Accumulator的數值task只能對Accumulator進行累加操作,不能讀取它的值,只有Driver程序可以讀取Accumulator的值。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章