天天看點

從一個sql任務了解spark記憶體模型

之前是隻知道記憶體模型理論上是怎麼樣的,這次拿到一個具體的任務,具體的executor來做對照分析,加深了解,在調記憶體參數時,也能有個依據。

從一個sql任務了解spark記憶體模型

1、背景

下面是一個sql任務的executor界面:

從一個sql任務了解spark記憶體模型

該任務運作沒有報oom,能正夠正常執行完畢,但觀察executor Summary頁面,有大量executor GC時間過長(GC時長已經超過總任務時長的10%,一般GC時長建議控制在總任務時長的5%以内)。

2、分析

先給出相關的參數(目前所在平台預設參數):

  • spark.executor.memory=8G
  • spark.executor.memoryOverhead=6144(6G)
  • spark.memory.fraction=0.6

觀察每個stage及job,并沒有資料傾斜現象。

以某個Executor為例:

Dtop mem:

從一個sql任務了解spark記憶體模型

從圖上可以看到,used_mem為8.96G 左右(最大為9.85G),alloc_mem:14.34G。

會覺得:哎呀,記憶體看上去還可以呀,挺充足呀,怎麼就GC了呢?

實際上dtop頁面的:

alloc_mem=executor memory(8G)+ memoryOverhead(6G)

used_mem=jvm實際使用量+overhead實際使用量

overhead我們管不着,那看看jvm heap的情況吧。

monitor 的jvm相關:

從一個sql任務了解spark記憶體模型

主要看三個參數:

  • max_heap:表示可用的最大記憶體
  • commited_heap: JVM 堆已 commit 的記憶體(包括實際配置設定的實體記憶體和未實際配置設定的記憶體) commited_heap <= max_heap ,當used_heap 接近committed_heap的時候,heap就會grow up,直到等于max_heap
  • used_heap: jvm中活動對象占用的記憶體,即實際的實體使用記憶體

commited_heap已經為8G,達到最大極限了。

used_heap為5G左右,整個過程中,最大的能達到6.89G。

這時候,會不會又覺得,最大8G,現在最多也才用6.89G,還有1G的記憶體沒用啊?

回顧一下spark統一記憶體模型:

從一個sql任務了解spark記憶體模型

jvm堆内的記憶體分為四個部分(spark.memory.fraction=0.6):

  • reservedMemory:預留記憶體300M,用于保障spark正常運作
  • other memory:用于spark内部的一些中繼資料、使用者的資料結構、防止出現對記憶體估計不足導緻oom時的記憶體緩沖、占用空間比較大的記錄做緩沖;估算大小為2.3G(8G-300M)*0.6*0.5
  • execution:用于spark的計算:shuffle、sort、aggregation等這些計算時會用到的記憶體;估算大小為2.3G(8G-300M)*0.6*0.5
  • storage:主要用于rdd的緩存;3G(8G-300M)*0.4

其中如果計算是記憶體execution不足會向storage部分借,如果還是不夠就會spill到磁盤。

如果execution來借記憶體,storage會犧牲自己丢棄緩存來借給execution,storage也可以向execution借記憶體,但execution不會犧牲自己。

是以,我們可以認為計算記憶體execution 可用最大記憶體為4.6G

used_heap 包含了計算記憶體和 othermemory 、reservedmemory、storage 的真實使用量。

沒辦法看到othermemory部分的實際使用記憶體大小,但可以确定,富裕出來的1G左右的記憶體是othermemory 沒有用完的并且計算記憶體execution一定是不太夠用了,因為整個運作過程一直伴随着gc,并且gc的時間是越來越長:

從一個sql任務了解spark記憶體模型

最嚴重的是,在最後,老年代也發生了gc

3、總結

上面說了那麼多,也有點亂,總結一下:

判斷記憶體夠不夠,不能隻看總圖,要清楚記憶體分幾個子產品,幾個子產品分别起什麼作用。一般出現記憶體不夠用的地方是 shuffle時的計算記憶體,計算記憶體真實的可用記憶體大小并不是在dtop總圖上看到的那麼大。

如果spark.executor.memory=8G , 則計算記憶體可用最大為:4.6G

從上面分析,發現堆外記憶體堆最大使用量差不多2G,而預設的 spark.executor.memoryOverhead=6144(6G) 有點浪費

最後測試參數:

spark.executor.memory=12G

spark.executor.memoryOverhead=3072(3G)

set spark.memory.fraction=0.75

最合适

其中spark.memory.fraction 不能設定太高,測試時,要為othermemory留一些富裕記憶體,因為spark記憶體統計資訊收集是有延遲的,如果該值過大,且spill較重情況下,會導緻記憶體釋放不及時而OOM。

記憶體參數該設定多少,沒有确切計算方法,可以依據經驗設定,然後多次測試出最合适的值。