Spark內(nèi)存管理解析
Spark是一個(gè)基于內(nèi)存的分布式計(jì)算引擎,為了更為高效地利用內(nèi)存,并減少OOM等內(nèi)存問題,Spark對(duì)JVM內(nèi)存模型進(jìn)行了進(jìn)一步的管理規(guī)劃,在其之上實(shí)現(xiàn)了自己的內(nèi)存管理模型。本文將基于spark.memory
(https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/memory/package.scala)
包對(duì)Spark內(nèi)存管理機(jī)制進(jìn)行簡(jiǎn)要探索。
Spark內(nèi)存管理模型
與其他JVM進(jìn)程類似,Spark進(jìn)程的內(nèi)存可以分為兩部分,一是由spark.executor.memory指定的JVM堆內(nèi)內(nèi)存,另一部分是由spark.memory.offHeap.size指定的堆外內(nèi)存。這兩部分內(nèi)存邏輯上由MemoryManager
(https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/memory/MemoryManager.scala)
進(jìn)行統(tǒng)一規(guī)劃與分配。MemoryManager的內(nèi)存管理模式是bookkeeping式的,其并不實(shí)際負(fù)責(zé)內(nèi)存的申請(qǐng)與釋放,而是維護(hù)著進(jìn)程中全局內(nèi)存的使用狀況,并根據(jù)一定的策略,對(duì)空閑內(nèi)存進(jìn)行邏輯分配,以及決定是否溢寫(spill)已有數(shù)據(jù)到磁盤等。其中:
堆內(nèi)內(nèi)存
MemoryManager將堆內(nèi)內(nèi)存邏輯上劃分為三個(gè)主要內(nèi)存區(qū)域(物理上依然是JVM堆內(nèi)內(nèi)存模型,占用可以任意交叉):
Reserved?Memory
這一塊是系統(tǒng)預(yù)留的內(nèi)存,其大小硬編碼為300MB。這意味著這300M內(nèi)存是不計(jì)入Spark內(nèi)存區(qū)域大小的,并且除非重新編譯Spark或者設(shè)置spark.testing.reservedMemory參數(shù),否則這塊內(nèi)存大小是無法更改的。不過官方僅僅只是將spark.testing.reservedMemory配置作為測(cè)試用,并不推薦在生產(chǎn)環(huán)境中更改。記住這塊內(nèi)存僅僅只是被稱作“reserved”,事實(shí)上任何情況下都不能被Spark使用,但是它確定了Spark可以使用的內(nèi)存分配上限。即使你想將所有的Java?Heap給Spark來緩存數(shù)據(jù),reserved部分要保持空閑所以你沒法這樣做(其實(shí)并不是真的空閑,里面會(huì)存儲(chǔ)很多Spark內(nèi)部對(duì)象)。
User?Memory
這一部分內(nèi)存區(qū)域是為用戶編寫的Spark應(yīng)用邏輯而預(yù)留的內(nèi)存,其大小為:
(Java?Heap?-?Reserved?Memory)?*?(1.0?-?spark.memory.fraction)
該部分內(nèi)存的使用方式完全取決于編寫Spark應(yīng)用的用戶。用戶可以用其存儲(chǔ)轉(zhuǎn)換RDD過程中用到的中間數(shù)據(jù)結(jié)構(gòu)等。比如用戶重寫Spark?aggregation時(shí),可以用其來存儲(chǔ)mapPartition轉(zhuǎn)換過程中用到的hash?table。再次說明一下,這一塊內(nèi)存是User?Memory,存什么和怎么存都取決于開發(fā)者,Spark不會(huì)對(duì)這一部分內(nèi)存區(qū)域作限制及檢查。因而,應(yīng)用代碼中如果忽略了分界,使用了超過該區(qū)域大小的內(nèi)存,可能會(huì)導(dǎo)致OOM(out?of?memory)錯(cuò)誤。
Spark?Memory
MemoryManager實(shí)際管理的是Spark?Memory這一部分內(nèi)存區(qū)域,該部分內(nèi)存區(qū)域大小由spark.memory.fraction控制,默認(rèn)為0.6。MemoryManager將該部分區(qū)域進(jìn)一步劃分為兩個(gè)部分:
- Storage?Memory
該部分內(nèi)存可用于unroll?RDD,緩存RDD,以及緩存broadcast等的傳輸數(shù)據(jù)。其與Execution?Memory之間的邊界大小由spark.memory.storageFraction控制,默認(rèn)為0.5。
- Execution?Memory
該部分內(nèi)存用作shuffle,?joins,?sorts以及aggregations等操作的計(jì)算內(nèi)存。其由所有task共享。不過,對(duì)于各task可使用內(nèi)存,MemoryManager進(jìn)行了進(jìn)一步控制。對(duì)于具有N個(gè)task的進(jìn)程,其確保每個(gè)task在進(jìn)行spill之前,至少能獲得1?/?2N的內(nèi)存,但最多只能獲取1?/?N的內(nèi)存。
堆外內(nèi)存
堆外內(nèi)存是Spark在JVM內(nèi)存之外直接開辟的內(nèi)存空間,用于存儲(chǔ)經(jīng)過序列化的二進(jìn)制數(shù)據(jù)。由于該部分內(nèi)存不經(jīng)由JVM內(nèi)存管理模型進(jìn)行申請(qǐng)和釋放,因而MemoryManager可以精確控制這部分內(nèi)存的申請(qǐng)與釋放,以及存儲(chǔ)數(shù)據(jù)需要占用的內(nèi)存空間大小。所以,MemoryManager將該部分內(nèi)存區(qū)域簡(jiǎn)單地劃分為Storage?Memory和Execution?Memory兩部分。
統(tǒng)一內(nèi)存管理
對(duì)于Storage?Memory和Execution?Memory的邊界劃分,在Spark?1.6.0之前是由StaticMemoryManager
(https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/memory/StaticMemoryManager.scala)
控制的。其采用的是固定的劃分方式,在這種劃分方式下,即使其中一方有大量空閑內(nèi)存,另一方也無法占用。從而,往往導(dǎo)致在還有大量空閑內(nèi)存的情況下,依然有大量的磁盤溢寫,影響Spark的性能。為了提高內(nèi)存使用率,1.6.0版本的內(nèi)存管理模型引入了軟間隔(soft?boundary)機(jī)制,使得Storage?Memory和Execution?Memory之間得以共享Spark?Memory的空閑內(nèi)存。該內(nèi)存管理模式在UnifiedMemoryManager
(https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/memory/UnifiedMemoryManager.scala)
類中進(jìn)行了實(shí)現(xiàn)。具體地:
-?spark.storage.storageFraction參數(shù)設(shè)定了Storage?Memory和Execution?Memory的基本區(qū)域,默認(rèn)為0.5。該設(shè)定確定了雙方各自擁有的空間范圍。
-?雙方的空間都不足時(shí),則溢寫己方內(nèi)存到硬盤;若己方空間不足而對(duì)方空余時(shí),可借用對(duì)方的空間。(存儲(chǔ)空間不足是指不足以放下一個(gè)完整的Block)。
-?Execution?Memory的空間被對(duì)方占用后,可讓對(duì)方將占用的部分轉(zhuǎn)存到磁盤,然后"歸還"借用的空間。
-?Storage?Memory的空間被對(duì)方占用后,無法讓對(duì)方"歸還",因?yàn)樾枰紤]執(zhí)行過程中的很多因素,實(shí)現(xiàn)起來較為復(fù)雜。
總結(jié)
Spark的統(tǒng)一內(nèi)存管理機(jī)制,在一定程度上提高了內(nèi)存資源的利用率,降低了開發(fā)者維護(hù)及調(diào)優(yōu)內(nèi)存的難度。借助Spark的內(nèi)存管理模型,開發(fā)者可以不再需要過多關(guān)心Spark?Memory這一部分內(nèi)存的內(nèi)存管理。不過,開發(fā)者依然需要注意:
依據(jù)實(shí)際應(yīng)用場(chǎng)景,確定spark.memory.fraction,并且在應(yīng)用開發(fā)過程中,確保使用的User?Memory不超過(spark.executor.memory?-?300MB)?*?(1.0?-?spark.memory.fraction)。
對(duì)于需要大量存儲(chǔ)內(nèi)存或者執(zhí)行內(nèi)存的應(yīng)用,可通過spark.memory.offHeap.enabled開啟堆外內(nèi)存,并適當(dāng)配置堆外內(nèi)存大小spark.memory.offHeap.size。
由于Spark對(duì)堆內(nèi)內(nèi)存的管理受限于JVM垃圾回收機(jī)制的不確定性以及對(duì)非序列化對(duì)象內(nèi)存占用的近似估算,可能導(dǎo)致某一時(shí)刻實(shí)際使用內(nèi)存遠(yuǎn)超預(yù)期,從而其無法完全避免內(nèi)存溢出(OOM)。
本文只對(duì)Spark邏輯層面的內(nèi)存管理模型進(jìn)行了簡(jiǎn)要介紹,對(duì)于Spark具體存儲(chǔ)過程及執(zhí)行過程中的內(nèi)存操作,可基于MemoryStore
(https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/storage/memory/MemoryStore.scala)
和TaskMemoryManager
(https://github.com/apache/spark/blob/master/core/src/main/java/org/apache/spark/memory/TaskMemoryManager.java)
進(jìn)行更為深入的研究。
參考資源
Spark?Memory?Management(https://0x0fff.com/spark-memory-management/)
Apache?Spark?內(nèi)存管理詳解(https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-apache-spark-memory-management/index.html)
大數(shù)據(jù)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。