一.整體架構(gòu)
Spark的存儲介質(zhì)包括磁盤和內(nèi)存撩笆。
Spark的存儲采用了主從模型,存儲模塊使用了基于Netty的RPC消息通信方式抒线。BlockManagerMaster負(fù)責(zé)整個應(yīng)用程序運行期間的數(shù)據(jù)塊的元數(shù)據(jù)管理和維護(hù)班巩。BlockManager(Slave)負(fù)責(zé)將本地數(shù)據(jù)塊的狀態(tài)信息上報給BlockManagerMaster,同時接受從BlockManagerMaster傳過來的執(zhí)行命令嘶炭,如獲取數(shù)據(jù)塊狀態(tài)抱慌,刪除數(shù)據(jù)塊等命令。每個BlockManager中都存在數(shù)據(jù)傳輸通道眨猎,根據(jù)需要進(jìn)行遠(yuǎn)程數(shù)據(jù)的讀取和寫入抑进。
在應(yīng)用程序啟動期間,SparkContext會創(chuàng)建Driver端的SparkEnv睡陪,在該SparkEnv中實例化BlockManagerMaster寺渗,在BlockManagerMaster內(nèi)部創(chuàng)建消息通信的端點BlockManagerMasterEndpoint。
同理兰迫,在Executor啟動時信殊,也會創(chuàng)建SparkEnv,在該SparkEnv中實例化BlockManager和負(fù)責(zé)網(wǎng)絡(luò)數(shù)據(jù)傳輸服務(wù)的BlockTransferService汁果。BlockManager初始化過程中涡拘,會加入BlockManagerMasterEndpoint端點的引用,同時也會創(chuàng)建BlockManagerSlaveEndpoint端點须鼎,并把該端點的引用注冊到Driver中鲸伴,這樣Driver和Executor互相持有通信端點的引用,可以在應(yīng)用程序執(zhí)行過程中進(jìn)行消息通信晋控。BlockTransferService中使用了基于Netty的數(shù)據(jù)傳輸方式汞窗,該傳輸方式隱藏了集群間不同節(jié)點的消息傳輸操作,可以類似于本地數(shù)據(jù)操作方式進(jìn)行數(shù)據(jù)讀寫赡译,大大簡化了網(wǎng)絡(luò)間數(shù)據(jù)傳輸?shù)膹?fù)雜程度仲吏。
Spark存儲整體架構(gòu)圖如下:
1.數(shù)據(jù)塊位置信息的獲取
應(yīng)用程序在完成數(shù)據(jù)存儲后,后續(xù)的task在獲取遠(yuǎn)程節(jié)點數(shù)據(jù)蝌焚,獲取rdd執(zhí)行的首選位置等操作需要根據(jù)數(shù)據(jù)塊的編號查詢出數(shù)據(jù)塊所處的位置裹唆,此時發(fā)送getLocation或者getLocationMultipleBlockIds等消息給BlockManagerMasterEndpoint端點,通過對元數(shù)據(jù)的查詢獲取數(shù)據(jù)塊的位置信息只洒。
2.數(shù)據(jù)塊的刪除
Spark提供刪除RDD许帐,數(shù)據(jù)塊和廣播變量的方式,當(dāng)數(shù)據(jù)需要刪除時毕谴,提交刪除成畦。當(dāng)數(shù)據(jù)需要刪除時距芬,提交刪除消息給BlockManagerSlaveEndpoint端點,在該端點對應(yīng)發(fā)起刪除操作循帐,刪除操作一方面需要先通知Driver端刪除數(shù)據(jù)塊的元數(shù)據(jù)信息框仔,另一方面需要發(fā)送消息通知Executor,刪除對應(yīng)的物理數(shù)據(jù)拄养。這里需要注意這個先后順序离斩,以免物理數(shù)據(jù)刪除完畢之后,還存在元數(shù)據(jù)未刪除瘪匿。
3.數(shù)據(jù)塊的讀取
Executor的BlockManager接受到讀取數(shù)據(jù)的請求時候跛梗,根據(jù)數(shù)據(jù)塊所在的節(jié)點是否在本地,調(diào)用BlockManager的不同方法去處理柿顶,如果在本地則調(diào)用MemoryStore或者DiskStore中的取方法茄袖,進(jìn)行讀取,如果在遠(yuǎn)程嘁锯,則調(diào)用BlockTransferService的服務(wù)去遠(yuǎn)程節(jié)點上獲取數(shù)據(jù)。
4.數(shù)據(jù)塊的寫入
當(dāng)Executor的BlockManager接受到寫數(shù)據(jù)的請求時聂薪,如果不需要創(chuàng)建副本家乘,則調(diào)用BlockStore的接口方法去進(jìn)行處理,根據(jù)數(shù)據(jù)寫入的存儲類型藏澳,決定調(diào)用對應(yīng)的寫入方法仁锯。
二.存儲級別
Spark的內(nèi)部維護(hù)的存儲,還支持以內(nèi)存或者磁盤存儲的方式存儲RDD的數(shù)據(jù)集翔悠。通過調(diào)用RDD的persist或者cache來完成對應(yīng)的操作业崖。
在RDD第一次被計算時,persist方法會根據(jù)StorageLevel的參數(shù)值來采用不同的緩存(持久化)策略蓄愁。當(dāng)RDD的原本存儲級別為None或者新傳遞進(jìn)來的存儲級別值與原來的存儲級別相等時才進(jìn)行操作双炕。
persist操作是控制操作的一種,它只是改變了RDD的元數(shù)據(jù)信息撮抓,并沒由真正的進(jìn)行數(shù)據(jù)的存儲操作妇斤,真正進(jìn)行的是RDD的iterator方法,對應(yīng)cache來說丹拯,他是persist的一個特例站超,即persist中的StorageLevel的值為MEMERY-ONLY的情形。
在StorageLevel類中乖酬,根據(jù)useDisk死相,useMemory,useOffHeap咬像,deserialized算撮,replication這5個參數(shù)的組合双肤,Spark提供了12種存儲級別的持久化策略,可以將RDD持久化到內(nèi)存钮惠,磁盤和外部存儲系統(tǒng)茅糜,或者以序列化的方式持久化到內(nèi)存,還能夠在集群的不同節(jié)點之間存儲多個副本素挽。
Spark存儲級別如下所示
Storage Level | 描述 |
---|---|
MEMORY_ONLY | 默認(rèn)選項蔑赘,RDD的(分區(qū))數(shù)據(jù)直接以Java對象的形式存儲于JVM的內(nèi)存中,如果內(nèi)存空間不足预明,某些分區(qū)的數(shù)據(jù)將不會被緩存缩赛,需要在使用的時候重新計算。 |
MEMORY_AND_DISK | RDD的數(shù)據(jù)直接以Java對象的形式存儲于JVM的內(nèi)存中撰糠,如果內(nèi)存空間不中酥馍,某些分區(qū)的數(shù)據(jù)會被存儲至磁盤,使用的時候從磁盤讀取阅酪。 |
MEMORY_ONLY_SER | RDD的數(shù)據(jù)(Java對象)序列化之后存儲于JVM的內(nèi)存中(一個分區(qū)的數(shù)據(jù)為內(nèi)存中的一個字節(jié)數(shù)組)旨袒,相比于MEMORY_ONLY能夠有效節(jié)約內(nèi)存空間(特別是使用一個快速序列化工具的情況下),但讀取數(shù)據(jù)時需要更多的CPU開銷术辐;如果內(nèi)存空間不足砚尽,處理方式與MEMORY_ONLY相同。 |
MEMORY_AND_DISK_SER | 相比于MEMORY_ONLY_SER辉词,在內(nèi)存空間不足的情況下必孤,將序列化之后的數(shù)據(jù)存儲于磁盤。 |
DISK_ONLY | 僅僅使用磁盤存儲RDD的數(shù)據(jù)(未經(jīng)序列化)瑞躺。 |
MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc. | 以MEMORY_ONLY_2為例敷搪,MEMORY_ONLY_2相比于MEMORY_ONLY存儲數(shù)據(jù)的方式是相同的,不同的是會將數(shù)據(jù)備份到集群中兩個不同的節(jié)點幢哨,其余情況類似赡勘。 |
OFF_HEAP (experimental) | 與MEMORY_ONLY_SER類似,但是存儲在非堆的內(nèi)存中嘱么,需要開啟非堆內(nèi)存狮含。 |
這里需要注意,即使沒有使用persisit曼振,Spark在進(jìn)行shuffle的操作的時候也會自動的持久化某些中間數(shù)據(jù)几迄。這樣可以避免在有節(jié)點丟失的情況下,重復(fù)計算input冰评。對于 resulting RDD如果需要重復(fù)使用的話映胁, 強烈建議使用persist進(jìn)行持久化。
如何選擇存儲級別甲雅?
默認(rèn)情況下解孙,性能最高的當(dāng)然是MEMORY_ONLY坑填,但前提是你的內(nèi)存必須足夠足夠大,可以綽綽有余地存放下整個RDD的所有數(shù)據(jù)弛姜。因為不進(jìn)行序列化與反序列化操作脐瑰,就避免了這部分的性能開銷;對這個RDD的后續(xù)算子操作廷臼,都是基于純內(nèi)存中的數(shù)據(jù)的操作苍在,不需要從磁盤文件中讀取數(shù)據(jù),性能也很高荠商;而且不需要復(fù)制一份數(shù)據(jù)副本寂恬,并遠(yuǎn)程傳送到其他節(jié)點上。但是這里必須要注意的是莱没,在實際的生產(chǎn)環(huán)境中初肉,恐怕能夠直接用這種策略的場景還是有限的,如果RDD中數(shù)據(jù)比較多時(比如幾十億)饰躲,直接用這種持久化級別牙咏,會導(dǎo)致JVM的OOM內(nèi)存溢出異常。
如果使用MEMORY_ONLY級別時發(fā)生了內(nèi)存溢出属铁,那么建議嘗試使用MEMORY_ONLY_SER級別眠寿。該級別會將RDD數(shù)據(jù)序列化后再保存在內(nèi)存中,此時每個partition僅僅是一個字節(jié)數(shù)組而已焦蘑,大大減少了對象數(shù)量,并降低了內(nèi)存占用盒发。這種級別比MEMORY_ONLY多出來的性能開銷例嘱,主要就是序列化與反序列化的開銷。但是后續(xù)算子可以基于純內(nèi)存進(jìn)行操作宁舰,因此性能總體還是比較高的拼卵。此外,可能發(fā)生的問題同上蛮艰,如果RDD中的數(shù)據(jù)量過多的話腋腮,還是可能會導(dǎo)致OOM內(nèi)存溢出的異常。
如果純內(nèi)存的級別都無法使用壤蚜,那么建議使用MEMORY_AND_DISK_SER策略即寡,而不是MEMORY_AND_DISK策略。因為既然到了這一步袜刷,就說明RDD的數(shù)據(jù)量很大聪富,內(nèi)存無法完全放下。序列化后的數(shù)據(jù)比較少著蟹,可以節(jié)省內(nèi)存和磁盤的空間開銷墩蔓。同時該策略會優(yōu)先盡量嘗試將數(shù)據(jù)緩存在內(nèi)存中梢莽,內(nèi)存緩存不下才會寫入磁盤。
通常不建議使用DISK_ONLY和后綴為_2的級別:因為完全基于磁盤文件進(jìn)行數(shù)據(jù)的讀寫奸披,會導(dǎo)致性能急劇降低昏名,有時還不如重新計算一次所有RDD。后綴為_2的級別阵面,必須將所有數(shù)據(jù)都復(fù)制一份副本轻局,并發(fā)送到其他節(jié)點上,數(shù)據(jù)復(fù)制以及網(wǎng)絡(luò)傳輸會導(dǎo)致較大的性能開銷膜钓,除非是要求作業(yè)的高可用性嗽交,否則不建議使用。