Spark學習——問題排查

Hiway發表於2019-05-04

其他更多java基礎文章:
java基礎學習(目錄)


  • 控制shuffle reduce端緩衝大小以避免OOM
  • 解決JVM GC導致的shuffle檔案拉取失敗
  • 解決各種序列化導致的報錯
  • 解決yarn-client模式導致的網路卡流量激增問題
  • 解決yarn-cluster模式的JVM棧記憶體溢位問題

控制shuffle reduce端緩衝大小以避免OOM

Spark學習——問題排查
map端的task是不斷的輸出資料的,資料量可能是很大的。此時reduce端的task,並不是等到map端task將屬於自己的那份資料全部寫入磁碟檔案之後,再去拉取的。map端寫一點資料,reduce端task就會拉取一小部分資料,立即進行後面的聚合、運算元函式的應用。

每次reduece能夠拉取多少資料,就由buffer來決定。因為拉取過來的資料,都是先放在buffer中的。然後才用後面的executor分配的堆記憶體佔比(0.2),去進行後續的聚合、函式的執行。

快取預設是48MB,也許大多數時候,reduce端task一邊拉取一邊計算,不一定一直都會拉滿48M的資料。可能大多數時候,拉取個10M資料,就計算掉了。

大多數時候,也許不會出現什麼問題。但是有的時候,map端的資料量特別大,然後寫出的速度特別快。reduce端所有task,拉取的時候,全部達到自己的緩衝的最大極限值,緩衝,48M,全部填滿。這個時候,再加上你的reduce端執行的聚合函式的程式碼,可能會建立大量的物件。也許,一下子,記憶體就撐不住了,就會OOM。reduce端的記憶體中,就會發生記憶體溢位的問題。

這個時候,就應該減少reduce端task緩衝的大小。我寧願多拉取幾次,但是每次同時能夠拉取到reduce端每個task的數量,比較少,就不容易發生OOM記憶體溢位的問題。(比如,可以調節成12M)

解決JVM GC導致的shuffle檔案拉取失敗

有時會出現的一種情況,非常普遍,在spark的作業中;shuffle file not found。(spark作業中,非常非常常見的)而且,有的時候,它是偶爾才會出現的一種情況。有的時候,出現這種情況以後,會重新去提交stage、task。重新執行一遍,發現就好了。沒有這種錯誤了。

比如,executor的JVM程式,可能記憶體不是很夠用了。那麼此時可能就會執行GC。minor GC or full GC。總之一旦發生了JVM之後,就會導致executor內,所有的工作執行緒全部停止,比如BlockManager,基於netty的網路通訊。

下一個stage的executor,可能是還沒有停止掉的,task想要去上一個stage的task所在的exeuctor,去拉取屬於自己的資料,結果由於對方正在gc,就導致拉取了半天沒有拉取到。

就很可能會報出,shuffle file not found。但是,可能下一個stage又重新提交了stage或task以後,再執行就沒有問題了,因為可能第二次就沒有碰到JVM在gc了。

解決引數如下:

spark.shuffle.io.maxRetries 3
複製程式碼

第一個引數,意思就是說,shuffle檔案拉取的時候,如果沒有拉取到(拉取失敗),最多或重試幾次(會重新拉取幾次檔案),預設是3次。

spark.shuffle.io.retryWait 5s
複製程式碼

第二個引數,意思就是說,每一次重試拉取檔案的時間間隔,預設是5s鍾。

預設情況下,假如說第一個stage的executor正在進行漫長的full gc。第二個stage的executor嘗試去拉取檔案,結果沒有拉取到,預設情況下,會反覆重試拉取3次,每次間隔是五秒鐘。最多隻會等待3 * 5s = 15s。如果15s內,沒有拉取到shuffle file。就會報出shuffle file not found。

針對這種情況,我們完全可以進行預備性的引數調節。增大上述兩個引數的值,達到比較大的一個值,儘量保證第二個stage的task,一定能夠拉取到上一個stage的輸出檔案。避免報shuffle file not found。然後可能會重新提交stage和task去執行。那樣反而對效能也不好。

spark.shuffle.io.maxRetries 60
spark.shuffle.io.retryWait 60s
複製程式碼

最多可以忍受1個小時沒有拉取到shuffle file。只是去設定一個最大的可能的值。full gc不可能1個小時都沒結束吧。

這樣呢,就可以儘量避免因為gc導致的shuffle file not found,無法拉取到的問題。

解決各種序列化導致的報錯

用client模式去提交spark作業,觀察本地列印出來的log。如果出現了類似於Serializable、Serialize等等字眼,報錯的log,就是碰到了序列化問題導致的報錯。雖然是報錯,但是序列化報錯,應該是屬於比較簡單的了,很好處理。

序列化報錯要注意的三個點:

  1. 你的運算元函式裡面,如果使用到了外部的自定義型別的變數,那麼此時,就要求你的自定義型別,必須是可序列化的。
final Teacher teacher = new Teacher("leo");

studentsRDD.foreach(new VoidFunction() {
 
public void call(Row row) throws Exception {
  String teacherName = teacher.getName();
  ....  
}

});

public class Teacher implements Serializable {
  
}
複製程式碼
  1. 如果要將自定義的型別,作為RDD的元素型別,那麼自定義的型別也必須是可以序列化的
JavaPairRDD<Integer, Teacher> teacherRDD
JavaPairRDD<Integer, Student> studentRDD
studentRDD.join(teacherRDD)

public class Teacher implements Serializable {
  
}

public class Student implements Serializable {
  
}
複製程式碼
  1. 不在上述兩種情況下,去使用一些第三方的,不支援序列化的型別
Connection conn = 

studentsRDD.foreach(new VoidFunction() {
 
public void call(Row row) throws Exception {
  conn.....
}

});
複製程式碼

Connection是不支援序列化的

解決yarn-client模式導致的網路卡流量激增問題

yarn-client模式下,會產生什麼樣的問題呢?

由於我們們的driver是啟動在本地機器的,而且driver是全權負責所有的任務的排程的,也就是說要跟yarn叢集上執行的多個executor進行頻繁的通訊(中間有task的啟動訊息、task的執行統計訊息、task的執行狀態、shuffle的輸出結果)。

我們們來想象一下。比如你的executor有100個,stage有10個,task有1000個。每個stage執行的時候,都有1000個task提交到executor上面去執行,平均每個executor有10個task。接下來問題來了,driver要頻繁地跟executor上執行的1000個task進行通訊。通訊訊息特別多,通訊的頻率特別高。執行完一個stage,接著執行下一個stage,又是頻繁的通訊。

在整個spark執行的生命週期內,都會頻繁的去進行通訊和排程。所有這一切通訊和排程都是從你的本地機器上發出去的,和接收到的。這是最要人命的地方。你的本地機器,很可能在30分鐘內(spark作業執行的週期內),進行頻繁大量的網路通訊。那麼此時,你的本地機器的網路通訊負載是非常非常高的。會導致你的本地機器的網路卡流量會激增!!!

你的本地機器的網路卡流量激增,當然不是一件好事了。因為在一些大的公司裡面,對每臺機器的使用情況,都是有監控的。不會允許單個機器出現耗費大量網路頻寬等等這種資源的情況。運維人員。可能對公司的網路,或者其他(你的機器還是一臺虛擬機器),對其他機器,都會有負面和惡劣的影響。

解決yarn-cluster模式的JVM棧記憶體溢位問題

實踐經驗,碰到的yarn-cluster的問題:

有的時候,執行一些包含了spark sql的spark作業,可能會碰到yarn-client模式下,可以正常提交執行;yarn-cluster模式下,可能是無法提交執行的,會報出JVM的PermGen(永久代)的記憶體溢位,OOM。

yarn-client模式下,driver是執行在本地機器上的,spark使用的JVM的PermGen的配置,是本地的spark-class檔案(spark客戶端是預設有配置的),JVM的永久代的大小是128M,這個是沒有問題的;但是呢,在yarn-cluster模式下,driver是執行在yarn叢集的某個節點上的,使用的是沒有經過配置的預設設定(PermGen永久代大小),82M。

spark-sql,它的內部是要進行很複雜的SQL的語義解析、語法樹的轉換等等,特別複雜,在這種複雜的情況下,如果說你的sql本身特別複雜的話,很可能會比較導致效能的消耗,記憶體的消耗。可能對PermGen永久代的佔用會比較大。

所以,此時,如果對永久代的佔用需求,超過了82M的話,但是呢又在128M以內;就會出現如上所述的問題,yarn-client模式下,預設是128M,這個還能執行;如果在yarn-cluster模式下,預設是82M,就有問題了。會報出PermGen Out of Memory error log。

如何解決這種問題?

既然是JVM的PermGen永久代記憶體溢位,那麼就是記憶體不夠用。我們們呢,就給yarn-cluster模式下的,driver的PermGen多設定一些。

spark-submit指令碼中,加入以下配置即可:

--conf spark.driver.extraJavaOptions="-XX:PermSize=128M -XX:MaxPermSize=256M"
複製程式碼

這個就設定了driver永久代的大小,預設是128M,最大是256M。那麼,這樣的話,就可以基本保證你的spark作業不會出現上述的yarn-cluster模式導致的永久代記憶體溢位的問題。

相關文章