Spark MetadataFetchFailedException 問題排查

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

一、問題描述

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

在這里插入圖片描述

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

后面application又自動(dòng)進(jìn)行重試,但是依舊失敗了,因此判斷這不是偶然的情況。

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

二、問題定位

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

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

隨便找了臺(tái)Lost Executor的日志,確實(shí)有些問題:

在這里插入圖片描述

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

Executor異常退出的原因猜測

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

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

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

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

2、linux OOMKiller

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

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

3、因磁盤問題Executor被yarn Kill

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

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

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

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

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

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

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

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

后面無意看到一個(gè)成功的Stage的某個(gè)失敗Task日志,才知道yarn會(huì)因?yàn)閮?nèi)存問題會(huì)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等開銷,因此沒做限制。

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

問題總結(jié)

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

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

三、解決方案

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

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

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

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

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

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

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

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

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

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

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

解決辦法:

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

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

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

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

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

解決方法:

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

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

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