最強Spark內存管理剖析,值得收藏~
今天和大家介紹Spark的內存模型,干貨多多,不要錯過奧~
與數據頻繁落盤的Mapreduce引擎不同,Spark是基于內存的分布式計算引擎,其內置強大的內存管理機制,保證數據優先內存處理,并支持數據磁盤存儲。
本文將重點探討Spark的內存管理是如何實現的,內容如下:
Spark內存概述
Spark 內存管理機制
Spark on Yarn模式的內存分配
今天和大家介紹Spark的內存模型,干貨多多,不要錯過奧~
與數據頻繁落盤的Mapreduce引擎不同,Spark是基于內存的分布式計算引擎,其內置強大的內存管理機制,保證數據優先內存處理,并支持數據磁盤存儲。
本文將重點探討Spark的內存管理是如何實現的,內容如下:
Spark內存概述
Spark 內存管理機制
Spark on Yarn模式的內存分配
1 Spark內存概述
首先簡單的介紹一下Spark運行的基本流程。
用戶在Driver端提交任務,初始化運行環境(SparkContext等)
Driver根據配置向ResoureManager申請資源(executors及內存資源)
ResoureManager資源管理器選擇合適的worker節點創建executor進程
Executor向Driver注冊,并等待其分配task任務
Driver端完成SparkContext初始化,創建DAG,分配taskset到Executor上執行。
Executor啟動線程執行task任務,返回結果。
Spark在任務運行過程中,會啟動Driver和Executor兩個進程。其中Driver進程除了作為Spark提交任務的執行節點外,還負責申請Executor資源、注冊Executor和提交Task等,完成整個任務的協調調度工作。而Executor進程負責在工作節點上執行具體的task任務,并與Driver保持通信,返回結果。
由上可見,Spark的數據計算主要在Executor進程內完成,而Executor對于RDD的持久化存儲以及Shuffle運行過程,均在Spark內存管理機制下統一進行,其內運行的task任務也共享Executor內存,因此本文主要圍繞Executor的內存管理進行展開描述。
Spark內存分為堆內內存(On-heap Memory)和堆外內存(Off-heap Memory)。其中堆內內存基于JVM內存模型,而堆外內存則通過調用底層JDK Unsafe API。兩種內存類型統一由Spark內存管理模塊接口實現。
def acquireStorageMemory(...): Boolean //申請存儲內存 def acquireExecutionMemory(...): Long //申請執行內存 def releaseStorageMemory(...): Unit //釋放執行內存 def releaseStorageMemory(...): Unit //釋放存儲內存
1.1 Spark的堆內內存
Executo作為一個JVM進程,其內部基于JVM的內存管理模型。
Spark在其之上封裝了統一的內存管理接口MemoryManager,通過對JVM堆空間進行合理的規劃(邏輯上),完成對象實例內存空間的申請和釋放。保證滿足Spark運行機制的前提下,最大化利用內存空間。
這里涉及到的JVM堆空間概念,簡單描述就是在程序中,關于對象實例|數組的創建、使用和釋放的內存,都會在JVM中的一塊被稱作為"JVM堆"內存區域內進行管理分配。
Spark程序在創建對象后,JVM會在堆內內存中分配一定大小的空間,創建Class對象并返回對象引用,Spark保存對象引用,同時記錄占用的內存信息。
Spark中堆內內存參數有: -executor-memory或-spark-executor-memory。通常是任務提交時在參數中進行定義,且與-executor-cores等相關配置一起被提交至ResourceManager中進行Executor的資源申請。
在Worker節點創建一定數目的Executor,每個Executor被分配-executor-memory大小的堆內內存。Executor的堆內內存被所有的Task線程任務共享,多線程在內存中進行數據交換。
Spark堆內內存主要分為Storage(存儲內存)、Execution(執行內存)和Other(其他) 幾部分。
Storage用于緩存RDD數據和broadcast廣播變量的內存使用
Execution僅提供shuffle過程的內存使用
Other提供Spark內部對象、用戶自定義對象的內存空間
Spark支持多種內存管理模式,在不同的管理模式下,以上堆內內存劃分區域的占比會有所不同,具體詳情會在第2章節進行描述。
1.2 Spark的堆外內存
Spark1.6在堆內內存的基礎上引入了堆外內存,進一步優化了Spark內存的使用率。
其實如果你有過Java相關編程經歷的話,相信對堆外內存的使用并不陌生。其底層調用基于C的JDK Unsafe類方法,通過指針直接進行內存的操作,包括內存空間的申請、使用、刪除釋放等。
Spark在2.x之后,摒棄了之前版本的Tachyon,采用Java中常見的基于JDK Unsafe API來對堆外內存進行管理。此模式不在JVM中申請內存,而是直接操作系統內存,減少了JVM中內存空間切換的開銷,降低了GC回收占用的消耗,實現對內存的精確管控。
堆外內存默認情況下是不開啟的,需要在配置中將spark.memory.offHeap.enabled設為True,同時配置spark.memory.offHeap.size參數設置堆大小。
對于堆外內存的劃分,僅包含Execution(執行內存)和Storage(存儲內存)兩塊區域,且被所有task線程任務共享。
2 Spark內存管理機制
前文說到,不同模式下的Spark堆內、堆外內存區域劃分占比是不同的。
在Spark1.6之前,Spark采用的是靜態管理(Static Memory Manager)模式,Execution內存和Storage內存的分配占比全部是靜態的,其值為系統預先設置的默認參數。
在Spark1.6后,為了考慮內存管理的動態靈活性,Spark的內存管理改為統一管理(Unified Memory Manager)模式,支持Storage和Execution內存動態占用。至于靜態管理方式任然被保留,可通過spark.memory.useLegacyMode參數啟用。
2.1 靜態內存管理(Static Memory Manager)
Spark最原始的內存管理模式,默認通過系統固定的內存配置參數,分配相應的Storage、Execution等內存空間,支持用戶自定義修改配置。
1. 堆內內存分配
堆內內存空間整體被分為Storage(存儲內存)、Execution(執行內存)、Other(其他內存)三部分,默認按照6:2:2的比率劃分。其中Storage內存區域參數: spark.storage.memoryFraction(默認為0.6),Execution內存區域參數: spark.shuffle.memoryFraction(默認為0.2)。Other內存區域主要用來存儲用戶定義的數據結構、Spark內部元數據,占系統內存的20%。
在Storage內存區域中,10%的大小被用作Reserved預留空間,防止內存溢出情況,由參數: spark.shuffle.safetyFraction(默認0.1)控制。90%的空間當作可用的Storage內存,這里是Executor進行RDD數據緩存和broadcast數據的內存區域,參數和Reserved一致。還有一部分Unroll區域,這一塊主要存儲Unroll過程的數據,占用20%的可用Storage空間。
Unroll過程:
RDD在緩存到內存之前,partition中record對象實例在堆內other內存區域中的不連續空間中存儲。RDD的緩存過程中, 不連續存儲空間內的partition被轉換為連續存儲空間的Block對象,并在Storage內存區域存儲,此過程被稱作為Unroll(展開)。
Execution內存區域中,20%的大小被用作Reserved預留空間,防止OOM和其他內存不夠的情況,由參數: spark.shuffle.safetyFraction(默認0.2)控制。80%的空間當作可用的Execution內存,緩存shuffle過程的中間數據,參數: spark.shuffle.safetyFraction(默認0.8)。
計算公式
可用的存儲內存 = systemMaxMemory * spark.storage.memoryFraction * spark.storage.safetyFraction 可用的執行內存 = systemMaxMemory * spark.shuffle.memoryFraction * spark.shuffle.safetyFraction
2. 堆外內存
相較于堆內內存,堆外內存的分配較為簡單。堆外內存默認為384M,由系統參數spark.yarn.executor.memoryOverhead設定。整體內存分為Storage和Execution兩部分,此部分分配和堆內內存一致,由參數: spark.memory.storageFaction決定。堆外內存一般存儲序列化后的二進制數據(字節流),在存儲空間中是一段連續的內存區域,其大小可精確計算,故此時無需設置預留空間。
3. 總結
實現機制簡單,易理解
容易出現內存失衡的問題,即Storage、Execution一方內存過剩,一方內容不足
需要開發人員充分了解存儲機制,調優不便
更多細節討論,歡迎添加我的個人- youlong525
2.2 統一內存管理(Unified Memory Manager)
為了解決(Static Memory Manager)靜態內存管理的內存失衡等問題,Spark在1.6之后使用了一種新的內存管理模式—Unified Memory Manager(統一內存管理)。在新模式下,移除了舊模式下的Executor內存靜態占比分配,啟用了內存動態占比機制,并將Storage和Execution劃分為統一共享內存區域。
1. 堆內內存
堆內內存整體劃分為Usable Memory(可用內存)和Reversed Memory(預留內存)兩大部分。其中預留內存作為OOM等異常情況的內存使用區域,默認被分配300M的空間。可用內存可進一步分為(Unified Memory)統一內存和Other內存其他兩部分,默認占比為6:4。
統一內存中的Storage(存儲內存)和Execution(執行內存)以及Other內存,其參數及使用范圍均與靜態內存模式一致,不再重復贅述。只是此時的Storage、Execution之間啟用了動態內存占用機制。
動態內存占用機制
設置內存的初始值,即Execution和Storage均需設定各自的內存區域范圍(默認參數0.5)
若存在一方內存不足,另一方內存空余時,可占用對方內存空間
雙方內存均不足時,需落盤處理
Execution內存被占用時,Storage需將此部分轉存硬盤并歸還空間
Storage內存被占用時,Execution無需歸還
2. 堆外內存
和靜態管理模式分配一致,堆外內存默認值為384M。整體分為Storage和Execution兩部分,且啟用動態內存占用機制,其中默認的初始化占比值均為0.5。
計算公式
可用的存儲&執行內存 = (systemMaxMemory -ReservedMemory) * spark.memoryFraction * spark.storage.storageFraction (啟用內存動態分配機制,己方內存不足時可占用對方)
3. 總結
動態內存占比,提升內存的合理利用率
統一管理Storage和Execution內存,便于調優和維護
由于Execution占用Storage內存可不規劃,存在Storage內存不夠頻繁GC的情況
3 Spark On Yarn模式的內存分配
由于Spark內存管理機制的健全,Executor能夠高效的處理節點中RDD的內存運算和數據流轉。而作為分配Executor內存的資源管理器Yarn,如何在過程中保證內存的最合理化分配,也是一個值得關注的問題。
首先看下Spark On Yarn的基本流程:
Spark Driver端提交程序,并向Yarn申請Application
Spark Driver端提交程序,并向Yarn申請Application
Yarn接受請求響應,在NodeManager節點上創建AppMaster
Yarn接受請求響應,在NodeManager節點上創建AppMaster
AppMaster向Yarn ResourceManager申請資源(Container)
AppMaster向Yarn ResourceManager申請資源(Container)
選擇合適的節點創建Container(Executor進程)
選擇合適的節點創建Container(Executor進程)
后續的Driver啟動調度,運行任務
后續的Driver啟動調度,運行任務
Yarn Client、Yarn Cluster模式在某些環節會有差異,但是基本流程類似。其中在整個過程中的涉及到的內存配置如下(源碼默認配置):
var executorMemory = 1024 val MEMORY_OVERHEAD_FACTOR = 0.10 val MEMORY_OVERHEAD_MIN = 384 // Executo堆外內存 val executorMemoryOverhead = sparkConf.getInt("spark.yarn.executor .memoryOverhead", math.max((MEMORY_OVERHEAD_FACTOR * executorMemory).toInt , MEMORY_OVERHEAD_MIN)) // Executor總分配內存 val executorMem= args.executorMemory + executorMemoryOverhead
因此假設當我們提交一個spark程序時,如果設置-executor-memory=5g。
spark-submit --master yarn-cluster --name test --executor-memory 5g --driver-memory 5g
根據源碼中的計算公式可得:
memoryMem= args.executorMemory(5120) + executorMemoryOverhead(512) = 5632M
然而事實上查看Yarn UI上的內存卻不是這個數值?這是因為Yarn默認開啟了資源規整化。
1. Yarn的資源規整化
Yarn會根據最小可申請資源數、最大可申請資源數和規整化因子綜合判斷當前申請的資源數,從而合理規整化應用程序資源。
定義
程序申請的資源如果不是該因子的整數倍,則將被修改為最小的整數倍對應的值
公式: ceil(a/b)*b (a是程序申請資源,b為規整化因子)
相關配置
yarn.scheduler.minimum-allocation-mb: 最小可申請內存量,默認是1024 yarn.scheduler.minimum-allocation-vcores: 最小可申請CPU數,默認是1 yarn.scheduler.maximum-allocation-mb: 最大可申請內存量,默認是8096 yarn.scheduler.maximum-allocation-vcores: 最大可申請CPU數,默認是4
回到前面的內存計算:由于memoryMem計算完的值為5632,不是規整因子(1024)的整數倍,因此需要重新計算:
memoryMem = ceil(5632/1024)*1024=6144M
2. Yarn模式的Driver內存分配差異
Yarn Client 和 Cluster 兩種方式提交,Executor和Driver的內存分配情況也是不同的。Yarn中的ApplicationMaster都啟用一個Container來運行;
Client模式下的Container默認有1G內存,1個cpu核,Cluster模式的配置則由driver-memory和driver-cpu來指定,也就是說Client模式下的driver是默認的內存值;Cluster模式下的dirver則是自定義的配置。
cluster模式(driver-memory:5g): ceil(a/b)*b可得driver內存為6144M
client模式(driver-memory:5g): ceil(a/b)*b可得driver內存為5120M
3. 總結
Apache Yarn作為分布式資源管理器,有自己內存管理優化機制。當在Yarn部署Spark程序時,需要同時考慮兩者的內存處理機制,這是生產應用中最容易忽視的一個知識點。
寫在最后
Spark內存管理機制是Spark原理和調優的重點內容,本文從Static Memory Manager(靜態管理模式)和Unified Memory Manager(統一管理模式)兩種模式入手,深入淺出的講解Spark計算模型是如何進行內存管理,其中在最后講述了Spark On Yarn的內存分配,希望以上內容能夠給大家帶來幫助。
》》》更多好文,請大家關注我的公眾號: 大數據兵工廠
spark 大數據 實時流計算服務 CS
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。