1. 程式人生 > >Spark 靜態記憶體管理

Spark 靜態記憶體管理

作者編輯:杜曉蝶,王瑋,任澤
 

Spark 靜態記憶體管理詳解

一、 內容簡介

      spark從1.6開始引入了動態記憶體管理模式,即執行記憶體和儲存記憶體之間可以互相搶佔。spark提供兩種記憶體分配模式,即:靜態記憶體管理和動態記憶體管理。該系列文章分別對這兩種記憶體管理模式的優缺點以及設計原理進行了分析。該篇文章主要針對spark1.6靜態記憶體管理進行了分析與說明。動態記憶體管理以及其他的調優文章後期會陸續為大家呈現,請大家關注furion。
此外本文會涉及到很多spark的概念,如果讀者對spark比較陌生,可以在閱讀本文前先了解一下基本概念,參考地址:

http://www.jianshu.com/p/e41b18a7e202 

二、 記憶體空間分配
 
      在 Spark 最初採用的靜態記憶體管理機制下,儲存記憶體、執行記憶體和其他記憶體的大小在 Spark 應用程式執行期間均為固定的,但使用者可以應用程式啟動前進行配置,堆內記憶體的分配如下圖所示:


1.png


      預設情況下,spark記憶體管理採用unified模式,如果要開啟靜態記憶體管理模式。將Spark.memory.useLegacyMode引數調為true(預設為false)。官網相關配置如下:


2.png


      當調整該引數以後,從SparkEnv.scala中可知,如果為true,記憶體管理呼叫靜態記憶體類(StaticMemoryManager)。反之,記憶體管理採用統一記憶體管理類(UnifiedMemoryManager)。


3.png


三、 Execution記憶體


  •  可用的Execution記憶體
 
      用於shuffle聚合記憶體,取決於joins,sorts,aggregations等過程中頻繁的IO需要的Buffer臨時資料儲存。
      簡單來說,spark在shuffle write的過程中,每個executor會將資料寫到該executor的物理磁碟上,下一個stage的task會去上一個stage拉取其需要處理的資料,並且是邊拉取邊進行處理的(和MapReduce的拉取合併資料基本一樣),這個時候會用一個aggregate的資料結構,比如hashmap邊拉取資料邊進行聚合。這部分記憶體就被稱做execution內

       從getMaxExecutironMemory方法可知,每個executor分配給execution的記憶體為:Execution Memory = systemMaxMemory * memoryFraction(預設0.2) * safetyFraction(預設0.8), 預設為 executor 最大可用記憶體 * 0.16。

 


4.png


      Execution記憶體在執行時會被分配給執行在JVM上的task。這裡不同的是,分配給每個task的記憶體並不是固定的,而是動態的。spark不是一上來就分配固定大小的記憶體塊給task,而是允許一個task佔據JVM所有execution記憶體。
每個JVM上的task可以最多申請至多1/N的execution記憶體(N為active task的個數,由spark.executor.cores指定)。如果task的申請沒有被批准,它會釋放一部分記憶體,並且下次申請的時候,它會申請更小的一部分記憶體。

       注意:為了防止過多的spilling(evict)資料,只有當一個task分配到的記憶體達到execution記憶體1/(2N)的時候才會spill, 如果目前空閒的記憶體達不到1/(2N)的時候, 記憶體申請會被阻塞直到其他的task spill掉它們的記憶體。如果不這樣限制,假設當前有一個任務佔據了絕大部分記憶體,那麼新來的task會一直往硬碟spill資料,這樣就會導致比較嚴重的I/O問題。
舉個例子, 某executor先啟動一個task A,並在task B啟動前快速佔用了所有可用記憶體。(B啟動後)N變成2,task B會阻塞直到task A spill,自己可獲得1/(2N)=1/4的execution記憶體。而一旦task B獲取到了1/4的記憶體,A和B就都有可能spill了。


  •  預留記憶體
 
      Spark之所以有一個SafetyFraction這樣的引數,是為了避免潛在的OOM。例如,進行計算時,有一個提前未預料到的比較大的資料,會導致計算時間延長甚至OOM, safetyFraction為storage和execution 都提供了額外的buffer以防止此類的資料傾斜。這部分記憶體叫做預留記憶體。

四、Storage記憶體

 


  • 可用的Storage記憶體
 
       該部分記憶體用作對RDD的快取(如呼叫cache,persist等方法),節點間傳輸的廣播變數。
從StaticMemoryManager的單例物件中可知,最後為每個executor分配到的關於storage的記憶體:StorageMemory=systemMaxMemory*storageMemoryFraction(預設0.6)*safetyFraction(預設為0.9)=0.54, 也就是說 預設分配executor 最大可用記憶體的 * 0.54。原始碼如下:

 


5.png


  • 預留記憶體
 
      同Execution記憶體中的預留部分

 


  • Unroll
 
      unroll是storage中比較特殊的一部分,它預設佔據總記憶體的20%。
      BlockManager是spark自己實現的內部分散式檔案系統,BlockManager接受資料(可能從本地或者其他結點)的時候是以iterator的形式,並且這些資料有序列化和非序列化的。需要注以下兩點:
       a) iterator在實體記憶體上是不連續的,如果後續spark要把資料裝載進記憶體的話,就需要把這些資料放進一個array(物理上連續)。
       b) 另外,序列化資料需要進行展開,如果直接展開序列化的資料,會造成OOM, 所以,BlockManager會逐漸的展開這個iterator,並逐漸檢查記憶體裡是否還有足夠的空間用來展開資料放進array裡。

 


6.png


        unroll的優先順序還是比較高的,它使用的記憶體空間可以從storage中借用,如果在storage中沒有現存的資料block,它甚至可以佔據整個storage空間。如果storage中有資料block,它可以最大drop掉記憶體的資料是以spark.storage.unrollFraction來控制的。由圖6可知,這部分預設為storage的20%。
       注意:這個20%的空間並不是靜態保留的,而是通過drop掉記憶體中的資料block來分配的。如果unroll失敗了,spark會把這部分資料evict 到硬碟。

四、 Other部分
 
      這片記憶體用於程式本身執行所需的記憶體,以及使用者定義的資料結構和建立的物件,此記憶體有上面兩部分決定,預設為0.2。

五、 侷限性

      spark的設計文件中指出靜態記憶體有以下侷限性: 
      (1) 沒有適用於所有應用的預設配置,通常需要開發人員針對不同的應用進行不同的引數配置。比如根據任務的執行邏輯,調整shuffle和storage記憶體佔比來適應任務的需求。
      (2) 這樣需要開發人員具備較高的spark原理知識。
      (3) 那些不cache資料的應用在執行時只佔用一小部分可用記憶體,因為預設的記憶體配置中,storage用去了safety記憶體的60%。

六、概念補充

      eviction策略:在spark技術文件中,eviction一詞經常出現。eviction並不是單純字面上驅逐的意思。說句題外話,spark我們通常都把它叫做記憶體計算框架,嚴格意義來說,spark並不是記憶體計算的新技術。無論是cache還是persist這類運算元,spark在記憶體安排上,絕大多數用的都是LRU策略(LRU可以說是一種演算法,也可以算是一種原則,用來判斷如何從Cache中清除物件,而LRU就是“近期最少使用”原則,當Cache溢位時,最近最少使用的物件將被從Cache中清除)。即當記憶體不夠的時候,會evict掉最遠使用過的記憶體資料block。當evict的時候,spark會將該資料塊evict到硬碟,而不是單純的拋棄掉。
無論是storage還是execution的記憶體空間,當記憶體區域的空間不夠用的時候,spark都會evict資料到硬碟。
因此,如果開發人員在記憶體分配上沒有合理的進行分配,無論是在storage還是execution超過記憶體的限制的時候,spark會把記憶體的資料寫到硬碟。如果是storage的情況,甚至可能把記憶體的資料全部寫到硬碟並釋放掉記憶體中的快取資料。這樣做,無疑會增加系統呼叫、I/O以及重複計算的開銷。有過開發spark任務中包含大量shuffle stage的同學應該有同感,shuffle memory不夠的時候,spill到硬碟的資料會很大,導致任務很慢,甚至會導致任務的各種重試最後任務fail掉。這種情況建議提高shuffle memory fraction。如果是資源排程在yarn上,建議通過spark.yarn.executor.memoryOverhead提高堆外記憶體,有的時候甚至會調到2g,3g,4g直到任務成功。

七、參考

       【1】 Unified Memory Management in Spark 1.6 ,Andrew Or and Josh Rosen
       【2】https://www.ibm.com/developerw ... erral
      【3】 http://spark.apache.org
      【4】 http://www.jianshu.com/p/e41b18a7e202