Spark —— Spark OOM Error問題排查定位

aof_發表於2020-11-22


Spark之所以能進行高效能的查詢計算,主要得益於其基於記憶體的計算模型,那麼在討論Spark 中的一系列OOM error之前,我們應該對Spark的記憶體模型有一個充分的瞭解(請參考: Spark記憶體模型),才能分析定位OOM出現的原因。Spark記憶體模型圖貼在這裡便於參考:
在這裡插入圖片描述
瞭解了Spark的記憶體模型之後,我們回到Spark OOM error上。當Spark應用程式因為OOM error中斷時,我們大概率會在報錯中看到諸如下面這樣的資訊:

java.lang.OutOfMemoryError: Java heap space

首先,我們要清楚的是根據Spark的執行架構可知,Spark OOM主要發生在兩個地方:Driver端和Executor端。

Driver端OOM Error

Driver端記憶體是通過配置“spark.driver.memory”來指定的,預設1g。注:在client模式下,此配置不能在應用程式中通過SparkConf設定,因為在SparkConf初始化之前,Driver JVM已經啟動了。這時,可以通過在spark-submit命令列指定–driver-memory來設定,或者配置到Spark的配置檔案(conf/spark_default.conf)中。

通常情況下,導致Driver端OOM的原因有以下兩種:

1. 不適合的API呼叫

比如,在大資料集(RDD/Dataset/DataFrame)上呼叫了collect()、collectAsList()、take()、takeAsList()等方法。這些方法會將所有要返回的資料從所有的Executor上傳送到Driver端,如果返回的資料超過了Driver記憶體,就會引發OOM Error。

針對這種情況,我們有以下3中解決方法:

  1. 最簡單粗暴的就是增加Driver端記憶體。
  2. 在RDD/Dataset上呼叫repartition()方法,將資料交給Executor上的一個任務處理。因為一般來講,Executor會設定較多的記憶體。
  3. 可以設定“spark.driver.maxResultSize”(預設1g)來避免Driver出現OOM errors。

2. 廣播了大變數

將RDD/Dataset進行廣播時,會先將它們傳送到Driver,然後由Driver端分發給每個Executor,如果我們的應用程式中廣播了多個大變數,超出了Driver記憶體限制,就可能造成OOM Error。

針對這種情況,我們有以下2中解決方法:

  1. 增加Driver端記憶體。
  2. 通過配置“spark.sql.autoBroadcastJoinThreshold”減小要廣播的變數的大小。

Executor端OOM Error

Executor端記憶體是通過配置“spark.executor.memory”來指定的,預設1g。

Executor端OOM Error原因:

1. 低效的查詢

比如,在列式儲存格式的表上執行select * from t返回不必要的列;沒有提前使用過濾條件過濾掉不必要的資料。

所以,我們在查詢的時候,要儘量將過濾條件前置;要儘量只讀需要的列;分割槽表上只查詢指定分割槽的資料等等。

2. 不合適的Driver端和Executor端記憶體

每個Spark應用程式所需的記憶體都是不一樣的,因此我們要結合所處理的資料的大小和應用監控(比如,Spark Web UI)來合理的為每個應用配置合適的Driver端和Executor端記憶體大小。

3. 不合適的YARN Container記憶體

當我們的Spark任務執行在YARN上的時候,可能會因為container記憶體負載造成OOM Error,出現類似如下的報錯:

......is running beyond physical memory limits. Current usage: xxxGB of xxxGB physical memory used; xxxGB of xxxGB virtual memory used. Killing container.....

這種情況我們需要給配置spark.yarn.executor.memoryOverhead(Spark 2.3.0之前)或spark.executor.memoryOverhead(Spark 2.3.0及之後)設定一個合適的值。此配置代表分配給每個Executor的非堆記憶體空間,這些記憶體空間主要用於VM開銷、interned strings以及其他本地開銷,預設值為executorMemory * 0.10,最小不能低於384MB。

4. 記憶體中快取大量資料

如果Spark應用程式指定在記憶體中快取了大的RDD/Dataset,或者是快取了較多的RDD/Dataset,就有可能發生OOM Error:

val df: Dataset[Row] = spark.read.table("")
df.persist(StorageLevel.MEMORY_ONLY) //Dataset全部快取到記憶體中

val rdd: RDD[Int] = ...
rdd.cache() //RDD全部快取到記憶體中

Spark記憶體模型中,Execution記憶體和Storage記憶體的總大小為0.6*(heap space - 300MB),這總大小裡面兩者預設各佔一半,我們應用中快取的哪些資料集正是儲存在Storage記憶體區域的。如果Spark應用程式中計算較少,那麼我們可以通過spark.memory.storage適當調大Storage記憶體,或者通過配置spark.memory.fraction整體調大兩者的總記憶體。

5. 不合適任務並行度

假如我們的Spark應用程式中要讀取的表或檔案資料量比較大,而我們沒有配置合適的記憶體大小,並且分配了比較高的併發度和CPU核數,這些資料要在記憶體中計算,這就可能會造成Executor端OOM。

如果任務並行度較低,而某個任務又被分配了較多的資料,也會造成OOM。

參考

相關文章