Spark 內存管理詳解(上):內存分配

本文轉自:Spark 內存管理詳解(上)——內存分配

Spark作爲一個基於內存的分佈式計算引擎,其內存管理模塊在整個系統中佔據着非常重要的角色。理解Spark內存管理的基本原理,有助於更好地開發Spark應用程序和進行性能調優。本文旨在梳理出Spark內存管理的脈絡,拋磚引玉,引出讀者對這個話題的深入探討。本文中闡述的原理基於Spark 2.1版本,閱讀本文需要讀者有一定的Spark和Java基礎,瞭解RDD、Shuffle、JVM等相關概念。

在執行Spark的應用程序時,Spark集羣會啓動Driver和Executor兩種JVM進程,前者爲主控進程,負責創建Spark上下文,提交Spark作業(Job),並將作業轉化爲計算任務(Task),在各個Executor進程間協調任務的調度,後者負責在工作節點上執行具體的計算任務,並將結果返回給Driver,同時爲需要持久化的RDD提供存儲功能[1]。由於Driver的內存管理相對來說較爲簡單,本文主要對Executor的內存管理進行分析,下文中的Spark內存均特指Executor的內存。

圖1 Spark的Driver和Worker

1 堆內和堆外內存

作爲一個JVM進程,Executor的內存管理建立在JVM的內存管理之上,Spark對JVM的堆內(On-heap)空間進行了更爲詳細的分配,以充分利用內存。同時,Spark引入了堆外(Off-heap)內存,使之可以直接在工作節點的系統內存中開闢空間,進一步優化了內存的使用。

圖2 堆外和堆內內存

1.1 堆內

堆內內存的大小,由Spark應用程序啓動時的–executor-memoryspark.executor.memory參數配置。Executor內運行的併發任務共享JVM堆內內存,這些任務在緩存RDD和廣播(Broadcast)數據時佔用的內存被規劃爲存儲(Storage)內存,而這些任務在執行Shuffle時佔用的內存被規劃爲執行(Execution)內存,剩餘的部分不做特殊規劃,那些Spark內部的對象實例,或者用戶定義的Spark應用程序中的對象實例,均佔用剩餘的空間。不同的管理模式下,這三部分佔用的空間大小各不相同(下面第2小節介紹)。

Spark對堆內內存的管理是一種邏輯上的“規劃式”的管理,因爲對象實例佔用內存的申請和釋放都由JVM完成,Spark只能在申請後和釋放前記錄這些內存:

  • 申請內存:
    • Spark在代碼中new一個對象實例
    • JVM從堆內內存分配空間,創建對象並返回對象引用
    • Spark保存該對象的引用,記錄該對象佔用的內存
  • 釋放內存:
    • Spark記錄該對象釋放的內存,刪除該對象的引用
    • 等待JVM的垃圾回收機制釋放該對象佔用的堆內內存

我們知道,JVM的對象可以以序列化的方式存儲,序列化的過程是將對象轉換爲二進制字節流,本質上可以理解爲將非連續空間的鏈式存儲轉化爲連續空間或塊存儲,在訪問時則需要進行序列化的逆過程——反序列化,將字節流轉化爲對象,序列化的方式可以節省存儲空間,但增加了存儲和讀取時候的計算開銷。

對於Spark中序列化的對象,由於是字節流的形式,其佔用的內存大小可直接計算,而對於非序列化的對象,其佔用的內存是通過週期性地採樣近似估算而得,即並不是每次新增的數據項都會計算一次佔用的內存大小,這種方法降低了時間開銷但是有可能誤差較大,導致某一時刻的實際內存有可能遠遠超出預期[2]。此外,在被Spark標記爲釋放的對象實例,很有可能在實際上並沒有被JVM回收,導致實際可用的內存小於Spark記錄的可用內存。所以Spark並不能準確記錄實際可用的堆內內存,從而也就無法完全避免內存溢出(OOM, Out of Memory)的異常。

雖然不能精準控制堆內內存的申請和釋放,但Spark通過對存儲內存和執行內存各自獨立的規劃管理,可以決定是否要在存儲內存裏緩存新的RDD,以及是否爲新的任務分配執行內存,在一定程度上可以提升內存的利用率,減少異常的出現。

1.2 堆外

爲了進一步優化內存的使用以及提高Shuffle時排序的效率,Spark引入了堆外(Off-heap)內存,使之可以直接在工作節點的系統內存中開闢空間,存儲經過序列化的二進制數據。利用JDK Unsafe API(從Spark 2.0開始,在管理堆外的存儲內存時不再基於Tachyon,而是與堆外的執行內存一樣,基於JDK Unsafe API實現[3]),Spark可以直接操作系統堆外內存,減少了不必要的內存開銷,以及頻繁的GC掃描和回收,提升了處理性能。堆外內存可以被精確地申請和釋放,而且序列化的數據佔用的空間可以被精確計算,所以相比堆內內存來說降低了管理的難度,也降低了誤差。

在默認情況下堆外內存並不啓用,可通過配置spark.memory.offHeap.enabled參數啓用,並由spark.memory.offHeap.size參數設定堆外空間的大小。除了沒有other空間,堆外內存與堆內內存的劃分方式相同,所有運行中的併發任務共享存儲內存和執行內存。

1.3 接口

Spark爲存儲內存和執行內存的管理提供了統一的接口——MemoryManager,同一個Executor內的任務都調用這個接口的方法來申請或釋放內存,同時在調用這些方法時都需要指定內存模式(MemoryMode),這個參數決定了是在堆內還是堆外完成這次操作。MemoryManager的具體實現上,Spark 1.6之後默認爲統一管理(Unified Memory Manager)方式,1.6之前採用的靜態管理(Static Memory Manager)方式仍被保留,可通過配置spark.memory.useLegacyMode參數啓用。兩種方式的區別在於對空間分配的方式,下面分別對這兩種方式進行介紹。

2 內存空間分配

2.1 靜態內存管理
堆內

在靜態內存管理機制下,存儲內存、執行內存和其他內存三部分的大小在Spark應用程序運行期間是固定的,但用戶可以在應用程序啓動前進行配置,堆內內存的分配如圖3所示:

圖3 靜態內存管理圖示——堆內

可以看到,可用的堆內內存的大小需要按照下面的方式計算:

可用的存儲內存 = systemMaxMemory * spark.storage.memoryFraction * spark.storage.safetyFraction
可用的執行內存 = systemMaxMemory * spark.shuffle.memoryFraction * spark.shuffle.safetyFraction

其中systemMaxMemory取決於當前JVM堆內內存的大小,最後可用的執行內存或者存儲內存要在此基礎上與各自的memoryFraction參數和safetyFraction參數相乘得出。上述計算公式中的兩個safetyFraction參數,其意義在於在邏輯上預留出1-safetyFraction這麼一塊保險區域,降低因實際內存超出當前預設範圍而導致OOM的風險(上文提到,對於非序列化對象的內存採樣估算會產生誤差)。值得注意的是,這個預留的保險區域僅僅是一種邏輯上的規劃,在具體使用時Spark並沒有區別對待,和“其它內存”一樣交給了JVM去管理。

堆外

堆外的空間分配較爲簡單,存儲內存、執行內存的大小同樣是固定的,如圖4所示:

圖4 靜態內存管理圖示——堆外

可用的執行內存和存儲內存佔用的空間大小直接由參數spark.memory.storageFraction決定,由於堆外內存佔用的空間可以被精確計算,所以無需再設定保險區域。

靜態內存管理機制實現起來較爲簡單,但如果用戶不熟悉Spark的存儲機制,或沒有根據具體的數據規模和計算任務或做相應的配置,很容易造成“一半海水,一半火焰”的局面,即存儲內存和執行內存中的一方剩餘大量的空間,而另一方卻早早被佔滿,不得不淘汰或移出舊的內容以存儲新的內容。由於新的內存管理機制的出現,這種方式目前已經很少有開發者使用,出於兼容舊版本的應用程序的目的,Spark仍然保留了它的實現。

2.2 統一內存管理

Spark 1.6之後引入的統一內存管理機制,與靜態內存管理的區別在於存儲內存和執行內存共享同一塊空間,可以動態佔用對方的空閒區域,如圖5和圖6所示:

圖5 統一內存管理圖示——堆內

圖6 統一內存管理圖示——堆外

動態佔用機制的規則如下:

  • 設定基本的存儲內存和執行內存區域(spark.storage.storageFraction參數),該設定確定了雙方各自擁有的空間的範圍
  • 雙方的空間都不足時,則存儲到硬盤;若己方空間不足而對方空餘時,可借用對方的空間;(存儲空間不足是指不足以放下一個完整的Block)
  • 執行內存的空間被對方佔用後,可讓對方將佔用的部分轉存到硬盤,然後“歸還”借用的空間
  • 存儲內存的空間被對方佔用後,無法讓對方“歸還”,因爲需要考慮Shuffle過程中的很多因素,實現起來較爲複雜

圖7 動態佔用機制圖解

小結

憑藉統一內存管理機制,Spark在一定程度上提高了堆內和堆外內存資源的利用率,降低了開發者維護Spark內存的難度,但並不意味着開發者可以高枕無憂。譬如,所以如果存儲內存的空間太大或者說緩存的數據過多,反而會導致頻繁的全量垃圾回收,降低任務執行時的性能,因爲緩存的RDD數據通常都是長期駐留內存的[5]。所以要想充分發揮Spark的性能,需要開發者進一步瞭解存儲內存和執行內存各自的管理方式和實現原理。

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