Spark Core 子模塊 storage分析

同步自己CSDN的文章:https://blog.csdn.net/don_chiang709/article/details/84065510

一、前言

1.相關版本:Spark Master branch(2018.10, compiled-version spark-2.5.0,?設置了spark.shuffle.sort.bypassMergeThreshold?? 1? 和?YARN-client 模式) ,HiBench-6.0 and Hadoop-2.7.1

2.建議先了解Spark 的 RDD、DAG、Memory 和 Shuffle基本概念。

3.重點介紹Spark 的Storage子模塊的組成部分,關鍵流程及與RDD、Memory和shuffle的相關的部分,目標是為了提高Spark在實際測試應用中的性能和穩定性提供分析依據。

畫圖工具(dia 0.97+git,http://live.gnome.org/Dia

二、Spark Core?子模塊 Storage 概要

1.?Spark 子模塊 Storage 管什么??當然還是管這倆個小伙伴:Memory and Disk (SSD & HDD)

?

?怎么管?以及有哪些應用場景?? 后續。。。

2. Spark core 子模塊 Storage 簡介

Storage 子模塊模塊負責RDD (persist(包括cache))Shuffle中間結果Broadcast變量的存儲及管理。

Storage模塊主要分為兩層:

①通信層:storage模塊采用的是master-slave結構來實現通信層,master和slave之間傳輸控制信息、狀態信息,這些都是通過通信層來實現的。

②存儲層:storage模塊需要把數據存儲到disk或是memory上面,有可能還需replicate到遠端,這都是由存儲層來實現和提供相應接口。

而其他Spark子模塊(RDD, Shuffle, Broadcast)要和Storage模塊進行交互,Storage模塊提供了統一的操作類 BlockManager,外部類與Storage模塊打交道都需要通過調用 BlockManager相應接口來實現。Storage模塊源代碼包含的目錄如下:

?

Spark core 子模塊 Storage 的類關系圖

?


三、Spark Core?子模塊 Storage 詳解

1.?Spark Storage框架圖(包括2個Slave)

?

上頁的圖是Spark Storage整體的架構圖.

1)Driver節點上的BlockManagerMaster 擁有 BlockManagerMasterEndpoint 的 actor 和所有 BlockManagerSlaveEndpoint 的ref, 可以通過這些引用對 slave 下達命令。

2)Executor 節點上的BlockManagerMaster 則擁有BlockManagerMasterEndpoint的ref和自身BlockManagerSlaveEndpoint的actor。可以通過 Master的引用注冊自己。

3)在master (driver)和 slave (executor)可以正常的通信之后, 就可以根據BlockManagerMessages定義的消息交互協議進行通信, 整個分布式緩存系統也就運轉起來了。

BlockManager對象創建簡介:

SparkEnv創建BlockManager對象時,使用了它的入參BlockManagerMaster對象,它發送的信息包裝成BlockManagerInfo。Spark在Driver和Executor端都創建各自的BlockManager對象,并通過BlockManagerMasterEndpoint/BlockManagerSlaveEndpoint進行通信,通過BlockManager的接口對Storage子模塊進行相關操作。

?

Driver BlockManager:

sparkEnv 在 master上啟動的時候, 構造了一個 BlockManagerMasterEndpoint, 然后把這個Endpoint 注冊在 rpcEnv中, 同時也會啟動自己的 BlockManager。

?

Executor BlockManager:

sparkEnv 在executor上啟動的時候, 通過 setupEndpointRef 方法獲取到了 BlockManagerMaster的引用 BlockManagerMasterRef, 同時也會啟動自己的 BlockManager。在 BlockManager 初始化自己的時候,會向 BlockManagerMasterEndpoint 注冊自己, BlockManagerMasterEndpoint 發送 registerBlockManager消息, BlockManagerMasterEndpoint 接受到消息, 把 BlockManagerSlaveEndpoint 的引用 保存在自己的 blockManagerInfo 數據結構中以待后用。

?

2.?Spark 子模塊 Storage 通信層(消息分布式協議)

1)?BlockManagerMasterEndpoint 接收的消息

BlockManagerMasterEndpoint從BlockManagerSlaveEndpoint接受到各種類型的消息, 以及接受到消息后的處理。


BlockManagerMasterEndpoint 代碼定義如下:

2)??BlockManagerSlaveEndpoint 接收的消息.

Slave的BlockManager在自己節點上存儲一個 Block, 然后把這個BlockId匯報到Master的BlockManager , 經過 cache, shuffle 或者 Broadcast后,別的節點需要這個Block的時候,會到 master 獲取數據所在位置, 然后去相應節點上去 fetch。

BlockManagerSlaveEndpoint 代碼定義如下:

3.Spark 子模塊Storage 存儲層

存儲層的關鍵類

?

存儲層的關鍵類

1) 存儲層里的 BlockManager 及關鍵角色介紹

BlockManager對象被創建的時候會創建出MemoryStore和DiskStore對象用以存取block, 如果StorageLevel包含了內存且內存中擁有足夠的內存, 就使用MemoryStore存儲, 如果不夠就 spill 到磁盤中, 通過DiskStore進行存儲。通過源碼可以看到目前BlockManager里的Block 類型列表如下:

?

BlockId是特定的Block 數據的唯一標識,通常關聯一個的文件。

MemoryStore 和 MemoryManager:

MemoryStore

相比DiskStore需要根據block id hash計算出文件路徑并將block存放到對應的文件里面, MemoryStore管理block就顯得非常簡單:MemoryStore內部維護了一個hash map來管理所有的block,以block id為key將block存放到hash map中。而從MemoryStore中取得block則非常簡單,只需從hash map中取出block id對應的value即可。

memorySore是基于JVM的堆內存來存儲數據,可以用于存數據的內存大小為:

(Runtime.getRuntime.maxMemory * memoryFraction * safetyFraction).toLong

其中memoryFraction 是可通過配置的一個比例(spark.storage.memoryFraction,默認0.6),safetyFraction是一個安全比例,可通過spark.storage.safetyFraction設置。

MemoryStore內部維護了一個hash map來管理所有的block,以block id為key將block存放到hash map中。

private val entries = new LinkedHashMap[BlockId, MemoryEntry[_]](32, 0.75f, true)

放內存就意味著要有足夠的內存來放,不然會導致OOM。

/**

?* Stores blocks in memory, either as Arrays of deserialized Java objects or as

?* serialized ByteBuffers.

?*/

private[spark] class MemoryStore(

??? conf: SparkConf,

??? blockInfoManager: BlockInfoManager,

??? serializerManager: SerializerManager,

??? memoryManager: MemoryManager,

??? blockEvictionHandler: BlockEvictionHandler)

? extends Logging {

關于LinkedHashMap的使用補充以下兩點

LinkedHashMap內存使用雙向鏈表維護數據的順序(訪問順序或插入順序),第三個參數為true時維護訪問順序,每次訪問的數據被移至雙向鏈表首位。

LinkedHashMap的value為MemoryEntry對象,Key為BlockId,BlockId有三個主要實現類,RDDBlockId、ShuffleBlockId、BroadcastBlockId,分別存儲RDD、Shuffle中間結果和Broadcast。

?MemoryManager

/**

?* An abstract memory manager that enforces how memory is shared between execution and storage.

?*

?* In this context, execution memory refers to that used for computation in shuffles, joins,

?* sorts and aggregations, while storage memory refers to that used for caching and propagating

?* internal data across the cluster. There exists one MemoryManager per JVM.

?*/

private[spark] abstract class MemoryManager(

??? conf: SparkConf,

??? numCores: Int,

??? onHeapStorageMemory: Long,

??? onHeapExecutionMemory: Long) extends Logging {

??? …

}

DiskStore 和 DiskBlockManager:

DiskStore

DiskSore就是基于磁盤介質來存取BlockManager的block數據,它提供了讀寫磁盤的接口getBytes/putByetes, getBytes對大于2M的block數據提供了MemoryMap。 DiskStore有一個成員DiskBlockManager,其主要作用就是邏輯block和磁盤block的映射,block的blockId對應磁盤文件中的一個文件。接收一個blockId和要寫的字節數據,通過blockId獲取到要寫的具體文件并得到對應的文件輸出流,將該bytes直接write這個流里,完成寫文件。

/**

?* Stores BlockManager blocks on disk.

?*/

private[spark] class DiskStore(

??? conf: SparkConf,

??? diskManager: DiskBlockManager,

??? securityManager: SecurityManager) extends Logging {

DiskBlockManager

DiskBlockManager 主要用來創建并持有邏輯 blocks 與磁盤上的 blocks之間的映射,一個邏輯 block 通過 BlockId(準確說是BlockId.name) 映射到一個磁盤上的文件。 在 DiskStore 中會調用 diskManager.getFile 方法, 如果子文件夾不存在,會進行創建, 文件夾的命名方式為(spark-local-yyyyMMddHHmmss-xxxx, xxxx是一個隨機數), 所有的block都會存儲在所創建的folder里面。

2) Spark 子模塊Storage 的Cache Manager

存儲層里隱藏的Cache Manager

①獨立的CacheManager.scala文件已離我們而去。

②Cache Manager 模塊的change-set:

CacheManager.scala已經在2.0.0版本中remove,對應的change-set為[SPARK-12817],描述如下:

[SPARK-12817]: Add BlockManager.getOrElseUpdate and remove CacheManager

Description: CacheManager directly calls MemoryStore.unrollSafely() and has its own logic for handling graceful fallback to disk when cached data does not fit in memory. However, this logic also exists inside of the MemoryStore itself, so this appears to be unnecessary duplication.? Thanks to the addition of block-level read/write locks in #10705, we can refactor the code to remove the CacheManager and replace it with an atomic `BlockManager.getOrElseUpdate()` method.?

所以CacheManager.scala代碼已經重構到BlockManager/MemoryStore.scala中了。

Cach Manager 概要:

?

Cache Manager 總結:

?

①每當Task 運行的時候會調用?RDD 的iterator?方法讀取RDD數據,而 iterator方法會通過 BlockManager來能獲取數據或者調用RDD子類的 Compute實現方法來計算;

②Cache Manager 管理的是緩存中的數據,緩存可以是基于內存的緩存(Cache()),也可以是基于磁盤的緩存(Persist(DISK_ONLY));

③Cache 在工作的時候會最大化的保留數據,但是數據不一定絕對完整,因為當前的計算如果需要內存空間的話,那么內存中的數據必需讓出空間,這是因為執行比緩存重要!此時如何在RDD 持久化的時候同時指定了可以把數據放左Disk 上,那么部份 Cache 的數據可以從內存轉入磁盤,否則的話,數據就會丟失!

④Cache Manager通過BlockManager來獲取數據的時候,優先在本地找數據或者的話就遠程抓取數據。

Cache、Persist 和 checkpoint的區別:

類型描述

Cache?Cache是 Persist(MEMORY_ONLY)。而Persist的入參可以設置成StorageLevel定義的任意一種,包括磁盤等。

Persist?生命周期:rdd.persist(StorageLevel.DISK_ONLY),將 RDD 的 partition 持久化到磁盤,但該 partition 由 blockManager管理。 一旦 driver program 執行結束,也就是 executor 所在進程 CoarseGrainedExecutorBackend stop,blockManager也會 stop, 被 cache 到磁盤上的 RDD 也會被清空(整個 blockManager使用的 local 文件夾被刪除)。

?RDD 的lineage未變

CheckPoint?生命周期:checkpoint 將 RDD 持久化到本地文件系統(local mode)或者HDFS (non-local mode)

?RDD 的lineage 已變,刪除之前的依賴關系,同時把父rdd設置成了CheckpointRDD

?需要 checkpoint 的 RDD 會被計算兩次, 正常的job 運行結束后(代碼在SparkContext.runJob里)會調用 finalRdd.doCheckpoint(),finalRdd會順著 computing chain 回溯掃描,碰到要 checkpoint 的 RDD 就將其標記為 CheckpointingInProgress,然后將寫磁盤.

?

Checkpoint的流程

heckp?oint的流程

CacheCche、Persist 和 checkpoint的區別、Persist 和 checkpoint的區別

四、Spark子模塊 Storage 里的應用案例分析 (分配 StorageMemory/ExecutionMemory/DiskStore)

1.?TaskBinary 使用 StorageMemory 的案例

回顧一下DAG的流程

?

TaskBinary使用 StorageMemory的案?

DAGSchedule里的Broadcast代碼

?

Executor端申請Storage類型的 Memory 流程

?

Broadcast 總結:

①broadcast 的是只讀變量

②Broadcase只讀變量到每個executor,可以被該executor上的所有 task 共享。

③每個 executor 都包含一個 blockManager用來管理存放在 executor 里的broadcast數據,數據StorageLevel為內存+磁盤。

④Driver 先建一個本地文件夾用以存放需要 broadcast 的 data,當需要broadcast時,先把 broadcast data 序列化到 byteArray,然后切割成 BLOCK_SIZE(由 spark.broadcast.blockSize = 4MB 設置)大小的 datablock,每個 data block 被 TorrentBlock對象持有。完成分塊切割后,就將分塊信息(稱為 meta 信息)存放到 driver 自己的 blockManager里面,StorageLevel為內存+磁盤,同時會通知 driver 自己的 blockManagerMaster說 metadata已經存放好。那么 driver submitTask() 的時候會將 bdata的metadata 和 func進行序列化得到 serialized task。

⑤Executor 收到 serialized task 后,先反序列化 task,這時候會反序列化 serialized task 中包含的 bdata類型是

TorrentBroadcast,也就是去調用 TorrentBroadcast.readObject()。這個方法首先得到 bdata對象,然后發現 bdata里面沒有包含實際的 data。怎么辦?先詢問所在的 executor 里的 blockManager是會否包含 data(通過查詢 data 的

broadcastId),包含就直接從本地 blockManager讀取 data。否則,就通過本地 blockManager去連接 driver 的

blockManagerMaster獲取 data 分塊的 meta 信息,獲取信息后,就開始了 BT 過程。

2。Shuffle Read 應用案例分析(ExecutionMemory)

回顧一下RDD的讀操作

?

回顧 Spark Shuffle 框架

?

ShuffleRead申請 Execution Memory流程

?

3。RDD persist 應用案例分析(DiskStore)

RDD 申請 DiskStore的代碼

①把hadoop目錄etc/hadoop下面的*-sit.xml復制到${SPARK_HOME}的conf下面.

②export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop

③spark-shell? --properties-file /home/yeshang/.local/bin/HiBench/report/terasort/spark/conf/sparkbench/spark.conf --master yarn

sc.setLogLevel("INFO")

////運行代碼

import org.apache.spark.storage.StorageLevel

valtextFile = spark.read.textFile("hdfs://localhost:9000/HiBench/Terasort/Input/part-m-00000")

textFile.persist(StorageLevel.MEMORY_AND_DISK)

textFile.count()

RDD 申請 DiskStore的流程:

?

五、總結:

Storage 是 Spark Core 的背后核心子模塊,沒有RDD、DAG的光環,但是擁有cache manager的功能(包括提供cache(),persist(), checkpoint()),是每次RDD.iterator的必經之路。

也是shuffle writer的寫或者排序,shuffle reader的緩存排序和計算完后的寫必經之路。由于Storage 關聯了DiskStore 和 MemoryStore(其中MemoryStore是構造 MemoryManager 的入參),Storage是 BlockManager 基礎上 Disk和 Memory 的管理者。其中MemoryManager可以延展解讀UnifiedMemoryManager 和 StaticMemoryManager,MemoryManager關聯的MemoryAllocator 又分為 HeapMemoryAllocator(OnHeap調用New array[long] 分配內存,標志 MemoryBlock為 FREED_IN_ALLOCATOR_PAGE_NUMBER 和 賦值MemoryBlock為NULL 釋放內存) 和 UnsafeMemoryAllocator(OffHeap調用Platform.allocateMemory和Platform.freeMemory接口分配和釋放內存)。詳情請見后續的文章“ Spark Core 的子模塊 Memory” 分析。

所以說 Storage 子模塊是RDD,Shuffle, Memory, Disk 和 DAG背后的合伙人

六、參考:

1.Spark source code https://github.com/apache/spark

2.圖解Spark核心技術與案例實戰

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容