Spark 內存調優以及 JVM 調優

Spark 內存調優以及 JVM 調優(基於源碼2.2.0分析)

目前Spark使用的內存管理模型有兩個,分別是:

  1. StaticMemoryManager
  2. UnifiedMemoryManager

而StaticMemoryManager是1.6之前的版本使用的內存管理模型.UnifiedMemoryManager是1.6之後使用的內存管理模型.
在SparkEvn中,通過spark.memory.useLegacyMode參數來進行內存管理模型的配置,默認false,即默認使用UnifiedMemoryManager.

val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode", false)
    val memoryManager: MemoryManager =
      if (useLegacyMemoryManager) {
        new StaticMemoryManager(conf, numUsableCores)
      } else {
        UnifiedMemoryManager(conf, numUsableCores)
      }

StaticMemoryManager

StaticMemoryManager模式下,分配給Spark的內存主要分爲3部分.

  1. Storage 部分,主要用於存儲 RDD 緩存的數據和 broadcast 廣播的數據,這部分內存佔給定 spark 內存的60%.其中這部分的10%用於Storage 內存的預留內存.剩下的90%用於存儲.而剩下的90%又劃分出20%的大小單獨用於緩存 iterator 形式的 Block 數據,即 unroll 部分.
  2. Shuffle 部分,這部分內存佔給定 spark 內存的20%,主要用於存儲 Shuffle 過程中的中間數據(或者可以說用於 Task 任務的計算),這部分也有20%的大小用於預留內存,80%的大小用於真正的計算用.
  3. Other 部分,這部分內存佔給定 spark 內存的20%,用於存儲用戶定義的數據結構或者 spark 內部的元數據.

StaticMemoryManager模式下,spark 各部分內存的分配圖:
spark 各部分內存的分配圖

UnifiedMemoryManager

UnifiedMemoryManager模式下,spark 內存主要分爲三塊:

  1. Storage&Execution,其中 Storage 主要用於存儲 RDD 緩存的數據和 broadcast 廣播的數據.Execution主要用於 Task 的計算(存儲 shuffle 中間數據).Storage 和 Execution 大小默認比例爲50%,但是,這是個軟邊界限制,也即當一方不足時,可以申請向另一方借用.Storage&Execution由spark.memory.fraction參數控制,默認60%.Storage與 Execution 佔比由spark.memory.storageFraction參數控制,默認各佔50%.
  2. User Memory,用戶內存區域,主要用於存儲 spark 的 RDD 轉換過程中使用的數據結構,官網上說的是 Spark完全沒有考慮你在那裏做什麼以及你是否尊重這個邊界.這部分內存的大小就是 spark 分配給用於存儲和計算的內存之外的內存.默認爲(1-spark.memory.fraction)
  3. Reserved Memory,系統保留內存,不在任何生產環境下使用.這塊區域的大小是默認值300M,不能更改.

UnifiedMemoryManager模式下,spark 各部分內存的分配圖:
UnifiedMemoryManager模式下,spark 各部分內存的分配圖

spark 內存調優參數:

參數 默認值 作用
spark.memory.useLegacyMode false 選用內存管理模式,默認爲 false,即默認使用 UnifiedMemoryManager,爲 true 時,使用StaticMemoryManager
spark.storage.safetyFraction 0.9 StaticMemoryManager 模式下,實際用於存儲的安全係數
spark.storage.memoryFraction 0.6 StaticMemoryManager 模式下,用於緩存 RDD 數據以及 broadcast 數據,實際大小爲systemMaxMemory*0.6*0.9*(1-unrollFraction)
spark.shuffle.safetyFraction 0.8 StaticMemoryManager 模式下實際用於計算的安全係數
spark.shuffle.memoryFraction 0.2 StaticMemoryManager 模式下,用於存儲 shuffle 中間數據(task 計算) 實際大小爲 systemMaxMemory * 0.2*0.8
spark.storage.unrollFraction 0.2 StaticMemoryManager 模式下,unroll 內存所佔比例,主要用於緩存 iterator 形式的 Block 數據,實際大小爲systemMaxMemory*0.6*0.9*unrollFraction
spark.memory.fraction 0.6 控制UnifiedMemoryManager模式下,用於存儲和計算的總內存,實際大小爲(systemMemory-300M)*0.6
spark.memory.storageFraction 0.5 控制UnifiedMemoryManager模式下,用於存儲和計算的內存佔比,實際大小爲(systemMemory-300M)*0.6*0.5.這是個軟邊界,當一方不足時,可以向另一方借用,比如 storage 不足時,可以向 execution 借用
spark.memory.offHeap.enabled false 是否使用堆外內存,默認是不使用堆外內存的,當開啓的時候,需要指定堆外內存的大小並且大於0
spark.memory.offHeap.size 0 堆外內存大小,當啓用堆外內存時,這個參數的值需要大於0,單位字節
spark.storage.replication.proactive false 默認是不啓用該參數的,如果啓用,則會在Executor故障丟失了rdd時,使用 RDD 的副本進行恢復,而不用重新開始計算. 如果開啓,將會使用額外的存儲空間.
spark.cleaner.periodicGC.interval 30min 控制觸發垃圾回收的頻率
spark.cleaner.referenceTracking true 是否啓用上下文清理.清理那些超出應用範圍的 RDD,Shuffle 對應的 map 任務狀態,Shuffle 元數據,Broadcast 對應以及 RDD 的 Checkpoint 數據
spark.cleaner.referenceTracking.blocking true 清理非Shuffle的其它數據是否是阻塞式的
spark.cleaner.referenceTracking.blocking.shuffle false 清理Shuffle數據是否是阻塞式的,清理MapOutputTracker中指定ShuffleId對應的map任務狀態和ShuffleManager中註冊的ShuffleId對應的Shuffle元數據。
spark.driver.extraJavaOptions - 用於設置 driver 端的 jvm 參數
spark.executor.extraJavaOptions - 用於設置 executor 端的 jvm 參數

JVM 參數調優

步驟:

  1. 在 添加打印 GC 日誌的參數:spark.executor.extraJavaOptions=-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
  2. 提交 spark 應用,查看花費時間最多的 stage 中,GC 花費時間最大的 task 任務的日誌.
  3. 查看 GC 日誌:
    3.1 如果一個task 任務完成之前發生了多次FullGC,說明沒有分配足夠多的內存,如果機器內存還足夠的話,可以增加executor-memory 參數的值.同時,在代碼中酌情較少 RDD 的緩存.在一個,jvm 的垃圾收集器使用更高效的 G1收集器
    3.2 增加年輕代的堆內存空間,這樣可以減少年輕代內存區域發生GC的次數,從而減少本不應該到老年代的對象的GC 年齡,這樣可以減少進入老年代的對象,從而減少老年代發生 fullGC 的頻率.設置新生代堆大小的參數:-Xmn,設置時,可以根據 Eden區的大小進行設置,大概爲 Eden 區大小的4/3倍.
    3.3 如果源數據是從HDFS讀取,則可以使用從HDFS讀取的數據塊的大小來估計任務使用的內存量。請注意,解壓縮塊的大小通常是塊大小的2或3倍。因此,如果我們希望有3或4個任務的工作空間,並且HDFS塊大小爲128 MB,我們可以估計Eden的大小4*3*128MB,則新生代的大小爲4*3*128*4/3MB

內存調優建議

  1. 使用高效的數據序列化類KryoSerializer
    • 參數:spark.serializer:org.apache.spark.serializer.KryoSerializer
  2. 設計數據結構時優先使用對象數組和基本類型,使用到集合類庫時,可以使用 fastutil 庫.
  3. 合理的使用內存管理以及內存管理參數設置
    1. 內存管理模型選擇:
      1. 如果我們的spark 應用計算比較複雜,沒法準確的預估計算和存儲所佔的比例,這時我們可以依賴UnifiedMemoryManager管理模型計算和存儲軟邊界的特性選擇使用UnifiedMemoryManager,這也是spark1.6之後系統默認的.
      2. 而如果我們 spark 應用計算業務邏輯時需要更大的存儲空間,比如代碼邏輯中使用頻繁的使用廣播變量,或者RDD 的 血統鏈比較長,需要使用 rdd 的 persist()等操作,這種情況下可以選擇使用StaticMemoryManager的固定內存管理模型比較好,並且需要適當的spark.storage.memoryFraction參數係數.
    2. 在使用StaticMemoryManager管理內存時,如果 spark 應用用到了大量的廣播或者 RDD 緩存,可以增加spark.storage.memoryFraction參數的值.反之,如果計算比較複雜,計算的過程中很少將 RDD 進行緩存,可以減小spark.storage.memoryFraction的值,同時增加spark.shuffle.memoryFraction的值.
    3. 在使用UnifiedMemoryManager管理內存時,如果計算比較複雜,可以將參數spark.memory.fraction稍微調大,同時調整spark.memory.storageFraction的值,默認是0.5,可以根據實際情況調整爲如0.4(雖然在計算的過程中可以進行相互借用)
  4. jvm 參數優化
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章