Spark的堆內內存
Spark之所以比MR快百倍,就是因為是基于內存迭代式計算的,于是就有了DAG有向無環圖.所以搞清楚了Spark是怎么管理內存的,對我們后期Spark調優、性能有更深的理解
Spark的內存分為堆內內存 和 堆外內存
下圖中的On-heap Memory為堆內內存,Off-heap Memory為堆外內存.
為什么要堆內內存?
Spark要通過對存儲內存和執行內存各自獨立的規劃管理,可以決定是否要在存儲內存里緩存新的RDD,以及是否為新的任務分配執行內存,在一定程度上可以提升內存的利用率,減少異常的出現。堆內內存的大小,由Spark應用程序啟動時的–executor-memory
同時,堆內內存從內存分配上可以分為 靜態方式 和 統一方式
堆內內存的靜態方式:
堆內內存的統一方式:
申請堆內內存流程:
1. Spark在代碼中new一個對象實例
2. JVM從堆內內存分配空間,創建對象并返回對象引用
3. Spark保存該對象的引用,記錄該對象占用的內存
釋放堆內內存流程
1. Spark記錄該對象釋放的內存,刪除該對象的引用
2. 等待JVM的垃圾回收機制釋放該對象占用的堆內內存
Spark的堆外內存
為什么要堆外內存?
Spaek為了進一步優化內存的使用以及提高Shuffle時排序的效率,Spark引入了堆外(Off-heap)內存,使之可以直接在工作節點的系統內存中開辟空間,利用JDK Unsafe API, Spark可以直接操作系統堆外內存,減少了不必要的內存開銷,以及頻繁的GC掃描和回收,提升了處理性能。堆外內存可以被精確地申請和釋放,而且序列化的數據占用的空間可以被精確計算,所以相比堆內內存來說降低了管理的難度,也降低了誤差。
RDD的持久化機制
彈性分布式數據集(RDD)作為Spark最根本的數據抽象,是只讀的分區記錄(Partition)的集合,只能基于在穩定物理存儲中的數據集上創建,或者在其他已有的RDD上執行轉換(Transformation)操作產生一個新的RDD。轉換后的RDD與原始的RDD之間產生的依賴關系,構成了血統(Lineage)。憑借血統,Spark保證了每一個RDD都可以被重新恢復。但RDD的所有轉換都是惰性的,即只有當一個返回結果給Driver的行動(Action)發生時,Spark才會創建任務讀取RDD,然后真正觸發轉換的執行。
Task在啟動之初讀取一個分區時,會先判斷這個分區是否已經被持久化,如果沒有則需要檢查Checkpoint或按照血統重新計算。所以如果一個RDD上要執行多次行動,可以在第一次行動中使用persist或cache方法,在內存或磁盤中持久化或緩存這個RDD,從而在后面的行動時提升計算速度。事實上,cache方法是使用默認的MEMORY_ONLY的存儲級別將RDD持久化到內存,故緩存是一種特殊的持久化。堆內和堆外存儲內存的設計,便可以對緩存RDD時使用的內存做統一的規劃和管理
內存管理之多任務間的分配
Executor內運行的任務同樣共享執行內存,Spark用一個HashMap結構保存了任務到內存耗費的映射。每個任務可占用的執行內存大小的范圍為1/2N ~ 1/N,其中N為當前Executor內正在運行的任務的個數。每個任務在啟動之時,要向MemoryManager請求申請最少為1/2N的執行內存,如果不能被滿足要求則該任務被阻塞,直到有其他任務釋放了足夠的執行內存,該任務才可以被喚醒。
Shuffle的內存占用
執行內存主要用來存儲任務在執行Shuffle時占用的內存,Shuffle是按照一定規則對RDD數據重新分區的過程,我們來看Shuffle的Write和Read兩階段對執行內存的使用:
Shuffle Write
若在map端選擇普通的排序方式,會采用ExternalSorter進行外排,在內存中存儲數據時主要占用堆內執行空間。若在map端選擇Tungsten的排序方式,則采用ShuffleExternalSorter直接對以序列化形式存儲的數據排序,在內存中存儲數據時可以占用堆外或堆內執行空間,取決于用戶是否開啟了堆外內存以及堆外執行內存是否足夠。
Shuffle Read
在對reduce端的數據進行聚合時,要將數據交給Aggregator處理,在內存中存儲數據時占用堆內執行空間。如果需要進行最終結果排序,則要將再次將數據交給ExternalSorter處理,占用堆內執行空間。