Spark MetadataFetchFailedException 問題排查

https://blog.csdn.net/u013332124/article/details/93629816

一、問題描述

業(yè)務(wù)反饋某個任務(wù)運行失敗,看Spark History Server發(fā)現(xiàn)多個Stage報如下錯誤:

在這里插入圖片描述

從UI以及異常的信息來看,是下游Stage做Shuffle Read時發(fā)現(xiàn)數(shù)據(jù)丟失,之后上游Stage和本Stage都重新調(diào)度計算,但是重試又發(fā)生類似的情況,直到重試3次后整個application失敗。

后面application又自動進行重試,但是依舊失敗了,因此判斷這不是偶然的情況。

所以問題只有一個:為什么下游Stage要獲取數(shù)據(jù)時,找不到上游Stage輸出的數(shù)據(jù)了?

二、問題定位

shuffle的過程主要是上游Stage將數(shù)據(jù)寫到磁盤,然后下游Stage通過Executor的BlockManager來拉取數(shù)據(jù)。如果下游Stage要拉取數(shù)據(jù)時Executor已經(jīng)異常下線了,就會導(dǎo)致下游Stage拉取不到數(shù)據(jù)。這時候就會報MetadataFetchFailedException。

看了下Driver的日志,確實發(fā)現(xiàn)很多 Lost executor 日志(如果Executor不是Driver主動通知退出的,Driver發(fā)現(xiàn)Executor長時間沒有響應(yīng)就會輸出這條日志)。

隨便找了臺Lost Executor的日志,確實有些問題:

在這里插入圖片描述

這是Executor上最后的幾十行日志。一般task開始和結(jié)束都會輸出日志,但是在這個日志中,我們就看到某幾個Task開始運行的日志,之后日志就斷了。說明Executor在這個時候異常退出了。

Executor異常退出的原因猜測

1、OOM導(dǎo)致Executor異常退出

Executor異常退出的最大可能就是OOM了。這個任務(wù)配置的Executor內(nèi)存是10g,core數(shù)量是8個,一個task需要的cpu是1個,因此task并行度是8。如果task需要的內(nèi)存大于 10/8=1.25g的話,那很可能會導(dǎo)致OOM。

但是觀察了下spark History server上數(shù)據(jù)量的大小,發(fā)現(xiàn)各個stage處理的數(shù)據(jù)量都不大,最多幾十G,stage的task數(shù)量一般都是1000個,分?jǐn)傁聛硪粋€task也就處理幾百M那里,不至于導(dǎo)致OOM。當(dāng)然,不排除數(shù)據(jù)傾斜的問題。

最重要的是,如果Executor發(fā)生OOM,是會有出錯日志的,但是看了幾個Executor的日志,都沒找到相關(guān)的出錯日志。

2、linux OOMKiller

還有一種情況是linux在系統(tǒng)內(nèi)存使用緊張時會根據(jù)一些算法kill某些進程,Executor可能就會被kill掉。

后面看了下那個時間點機器的使用內(nèi)存,發(fā)現(xiàn)還有大量的剩余內(nèi)存。因此和OOMKiller無關(guān)

3、因磁盤問題Executor被yarn Kill

如果某個NodeManager在運行過程中工作磁盤使用率達(dá)到了一定的閥值,該NodeManager會被標(biāo)記為UnHealth,同時在上面運行的Container都會被停止。

用df -h命令檢查了下機器的磁盤使用情況,也沒什么問題。因此排除這種可能

和判斷NodeManager是否健康的配置有:

yarn.nodemanager.disk-health-checker.enable: 是否開啟磁盤健康檢查,默認(rèn)是true,表示開啟

yarn.nodemanager.disk-health-checker.min-healthy-disks:NodeManager上最少保證健康磁盤比例,當(dāng)健康磁盤比例低于該值時,NodeManager不會再接收和啟動新的Container,默認(rèn)值是0.25,表示25%;

yarn.nodemanager.disk-health-checker.max-disk-utilization-per-disk-percentage:一塊磁盤的最高使用率,當(dāng)一塊磁盤的使用率超過該值時,則認(rèn)為該盤為壞盤,不再使用該盤,默認(rèn)是90,表示90%,可以適當(dāng)調(diào)低;

yarn.nodemanager.disk-health-checker.min-free-space-per-disk-mb:一塊磁盤最少保證剩余空間大小,當(dāng)某塊磁盤剩余空間低于該值時,將不再使用該盤,默認(rèn)是0,表示0MB。

4、因內(nèi)存問題Executor被yarn Kill

后面無意看到一個成功的Stage的某個失敗Task日志,才知道yarn會因為內(nèi)存問題會kill Executor:

在這里插入圖片描述

異常信息也很明了,Executor使用的內(nèi)存超過了限制,因此被Yarn Kill掉。

spark.yarn.executor.memoryOverHead默認(rèn)配置是ExecutorMemory的10%,也就是1G。所以Executor在yarn這邊申請的內(nèi)存是11G。但是spark.yarn.executor.memoryOverHeap是堆外內(nèi)存,主要用于JVM自身,字符串, NIO Buffer等開銷,因此沒做限制。

如果運行的task數(shù)量過多,OverHead的使用就會超過1G,最終Executor總的使用內(nèi)存超過11G,yarn有個container內(nèi)存使用檢測機制,如果發(fā)現(xiàn)有container內(nèi)存使用超標(biāo),就會主動kill這個Container。

問題總結(jié)

這個問題的主要原因就是任務(wù)的task并行度是8,但是OverHead大小只有1G,在任務(wù)運行過程中不時的OverHead區(qū)域超過了1G,最終導(dǎo)致Executor被yarn給kill了。

舉個例子,比如某個上游Stage的task運行成功將數(shù)據(jù)寫入Executor A后,下游Stage的task又分發(fā)到這個Executor A上,這時運行過程中Executor A的OverHead區(qū)域使用超過了1G,整個Executor 被kill,這樣Executor A上的shuffle數(shù)據(jù)就丟失了。后面的重試又不斷的重復(fù)這樣的場景,直到整個任務(wù)失敗。

三、解決方案

通過調(diào)整任務(wù)的參數(shù)可以解決這個問題。這個問題的主要原因在于OverHead的大小。因此我們有兩個方向調(diào)整任務(wù)的參數(shù):

  1. 通過spark.yarn.executor.memoryOverHead調(diào)大OverHead的大小
  2. OverHead使用太多主要還是task并行度的原因,也可以調(diào)小task的并行度來降低overHead的使用

四、擴展:Executor因內(nèi)存問題被Yarn Kill的情況

Executor運行時會告訴yarn要申請的資源數(shù)量,如果要申請的內(nèi)存超過yarn配置的 yarn.scheduler.maximux-allocation-mb值,yarn就會拒絕Executor的啟動。

Executor啟動后,yarn也有 Container 的內(nèi)存監(jiān)控機制。如果運行過程中Executor實際使用的內(nèi)存超過了申請的內(nèi)存,yarn發(fā)現(xiàn)了就會主動kill 這個Executor。比如Executor申請資源時指定要10G,但是運行過程過實際使用超過了10G,那么yarn就會主動kill這個Executor。

有兩種情況可能導(dǎo)致Executor實際使用的內(nèi)存超過預(yù)期值:

1、Overhead 區(qū)域使用超過預(yù)期值

overhead的大小由spark.yarn.executor.memoryOverHead來配置,如果沒配置,默認(rèn)是ExecutorMemory的10%。

Executor向yarn申請內(nèi)存時會自動算上overhead,但是還是會有overhead大小不夠用的情況。

這種一般都發(fā)生在Executor同時運行的task數(shù)量比較多的情況,overhead區(qū)域主要用于JVM自身,字符串, NIO Buffer(Driect Buffer)等開銷,如果task并發(fā)度太高,就會導(dǎo)致overhead區(qū)域不夠用的情況。

因為overhead是堆外內(nèi)存,因此它不會受JVM內(nèi)存限制。但是overhead使用超過了預(yù)期的值后,yarn就開始kill這個 Executor了。

解決辦法:

  • 調(diào)大overhead的值
  • 降低task的并行度,task并行度有 spark.executor.cores / spark.task.cpus 決定

2、Executor又開啟了子進程導(dǎo)致總內(nèi)存使用超出預(yù)期

yarn對container內(nèi)存的判斷不單單只判斷Executor的內(nèi)存使用量,如果Executor又開啟了一個子進程,這個子進程使用的內(nèi)存也會算在Container的內(nèi)存使用里面。

也就是說,yarn對container內(nèi)存判斷算的是整個Executor進程樹的總內(nèi)存大小。

典型的場景就是Executor中又進行了shell調(diào)用,shell運行的子程序又占用了一定大小,最終導(dǎo)致超過了預(yù)期值,然后Executor被yarn kill。

解決方法:

  • 保證開啟的子進程的內(nèi)存使用,同時設(shè)置好合適的ExecutorMemory
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容