數(shù)據(jù)本地性對(duì) Spark 生產(chǎn)作業(yè)容錯(cuò)能力的負(fù)面影響

數(shù)據(jù)本地性是 Spark 等計(jì)算引擎從計(jì)算性能方面去考量的一個(gè)重要指標(biāo),對(duì)于某個(gè)數(shù)據(jù)分片的運(yùn)算,Spark 在調(diào)度側(cè)會(huì)做數(shù)據(jù)本地性的預(yù)測(cè),然后盡可能的將這個(gè)運(yùn)算對(duì)應(yīng)的Task調(diào)度到靠近這個(gè)數(shù)據(jù)分片的Executor上。

Spark 計(jì)算作業(yè)依賴于整個(gè)物理計(jì)算集群的穩(wěn)定性,拋開(kāi)軟件層,如資源管理層(YARN,Kubernetes),存儲(chǔ)層(HDFS)本身的穩(wěn)定性不說(shuō),Spark 依賴于物理機(jī)器上的 CPU、 內(nèi)存、 磁盤(pán)和網(wǎng)絡(luò)進(jìn)行真正的計(jì)算作業(yè)。單個(gè)物理機(jī)的硬件故障是一個(gè)小概率的事件,但當(dāng)集群的規(guī)模到達(dá)成百上千甚至過(guò)萬(wàn)臺(tái),那以集群為維度,大大小小的硬件故障將成為一個(gè)常態(tài)。

關(guān)鍵字:TaskLocality, 容錯(cuò), 已經(jīng)故障

1. Spark TaskLocality

在 Spark 中數(shù)據(jù)本地性通過(guò) TaskLocality 來(lái)表示,有如下幾個(gè)級(jí)別,

  • PROCESS_LOCAL
  • NODE_LOCAL
  • NO_PREF
  • RACK_LOCAL
  • ANY

從上到下數(shù)據(jù)本地性依次遞減。

Spark 在執(zhí)行前通過(guò)數(shù)據(jù)的分區(qū)信息進(jìn)行計(jì)算 Task 的 Locality,Task 總是會(huì)被優(yōu)先分配到它要計(jì)算的數(shù)據(jù)所在節(jié)點(diǎn)以盡可能地減少網(wǎng)絡(luò) IO。這個(gè)計(jì)算的過(guò)程通過(guò) spark.locality.wait 默認(rèn)為3s,控制這個(gè)計(jì)算的過(guò)程。

2. Spark 內(nèi)部容錯(cuò)

原理這里不細(xì)講,簡(jiǎn)而言之就是重試。Spark 規(guī)定了同一個(gè) Job 中同一個(gè) Stage 連續(xù)失敗重試的上限(spark.stage.maxConsecutiveAttempts),默認(rèn)為4,也規(guī)定了一個(gè) Stage 中 同一個(gè) Task 可以失敗重試的次數(shù)(spark.task.maxFailures),默認(rèn)為4。當(dāng)其中任何一個(gè)閾值達(dá)到上限,Spark 都會(huì)使整個(gè) Job 失敗,停止可能的“無(wú)意義”的重試。

3. 數(shù)據(jù)本地性和容錯(cuò)的沖突

我們首先來(lái)看一個(gè)例子,如圖所示,圖為 Spark Stage 頁(yè)面下 Task Page 的詳細(xì)視圖。

  • 第一列表示該 Task 進(jìn)行了4次重試,所以這個(gè) Task 對(duì)應(yīng)的 Job 也因此失敗了。
  • 第三列表示該 Task 的數(shù)據(jù)本地性,都是 NODE_LOCAL 級(jí)別,對(duì)于一個(gè)從HDFS讀取數(shù)據(jù)的任務(wù),顯然獲得了最優(yōu)的數(shù)據(jù)本地性
  • 第四列表示的是 Executor ID,我們可以看到我們?nèi)蝿?wù)的重試被分配到ID 為5和6兩個(gè) Executor 上
  • 第五列表示我們運(yùn)行這些重試的 Task 所在的 Executor 所在的物理機(jī)地址,我們可以看到他們都被調(diào)度到了同一個(gè)
  • 最后列表示每次重試失敗的錯(cuò)誤棧
Spark Stage 頁(yè)面下 Task Page 的詳細(xì)視圖

3.1 問(wèn)題一: 單個(gè) Task 重試為什么失敗?

結(jié)合硬件層面的排查,發(fā)現(xiàn)是 NodeManager 物理節(jié)點(diǎn)上掛在的 /mnt/dfs/4,出現(xiàn)硬件故障導(dǎo)致盤(pán)只讀,ShuffleMapTask 在即將完成時(shí),將index文件和data文件commit時(shí),獲取index的臨時(shí)文件時(shí)候發(fā)生FileNotFoundException

java.io.FileNotFoundException: /mnt/dfs/4/yarn/local/usercache/da_haitao/appcache/application_1568691584183_1953115/blockmgr-1b6553f2-a564-4b31-a4a6-031f21c9c30f/0a/shuffle_96_2685_0.index.82594412-1f46-465e-a067-2c5e386a978e (No such file or directory)
    at java.io.FileOutputStream.open0(Native Method)
    at java.io.FileOutputStream.open(FileOutputStream.java:270)
    at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
    at java.io.FileOutputStream.<init>(FileOutputStream.java:162)
    at org.apache.spark.shuffle.IndexShuffleBlockResolver.writeIndexFileAndCommit(IndexShuffleBlockResolver.scala:144)
    at org.apache.spark.shuffle.sort.UnsafeShuffleWriter.closeAndWriteOutput(UnsafeShuffleWriter.java:245)
    at org.apache.spark.shuffle.sort.UnsafeShuffleWriter.write(UnsafeShuffleWriter.java:190)
    at org.apache.spark.scheduler.ShuffleMapTask.runTask(ShuffleMapTask.scala:96)
    at org.apache.spark.scheduler.ShuffleMapTask.runTask(ShuffleMapTask.scala:53)
    at org.apache.spark.scheduler.Task.run(Task.scala:109)

3.2 問(wèn)題二:為什么該 Task 的4次重試都在同一個(gè)物理節(jié)點(diǎn)?

這是由于 Driver 在調(diào)度該 Task 的時(shí)候進(jìn)行了數(shù)據(jù)本地性的運(yùn)算,而且在spark.locality.wait 默認(rèn)為3s的時(shí)間約束內(nèi)成功獲得了NODE_LOCAL級(jí)別的數(shù)據(jù)本地性,故而都調(diào)度到了同一個(gè) NodeManger 物理節(jié)點(diǎn)。

3.3 問(wèn)題三:為什么總是“本地重試”,不是“異地重試”?

這個(gè)過(guò)程從邏輯上講,其實(shí)已經(jīng)不是“本地重試”,而恰恰是“異地重試”了。這我們可以從4次的重試的 Executor ID 上進(jìn)行判斷,第0、1和3次是在 ID 6上進(jìn)行的,而第2次是在 ID 5上發(fā)生的。但由于ID 5和6都在同一個(gè) NodeManger 節(jié)點(diǎn),所以我們看起來(lái)像是“本地重試”。另一個(gè)原因就是上面所說(shuō)的數(shù)據(jù)本地性的成功解析,所以這些 Task 的每次重試都高概率的來(lái)到這個(gè)節(jié)點(diǎn)。
所有 Spark Task 級(jí)別的重試從邏輯上都應(yīng)該屬于“異地重試”,他們都需要通過(guò) Driver 重新調(diào)度到新的 Executor 進(jìn)行重試。我們所觀測(cè)到的“本地”和“異地”是屬于“現(xiàn)象”而非“本質(zhì)”,影響這種現(xiàn)象的條件有比如下面幾個(gè)(不一定全面):1. 數(shù)據(jù)本地性 2. Executor 由于 NodeLabel 限制,只在若干有限的物理機(jī)上分配 3. ResourceManager 調(diào)度時(shí)剛好把所有的 Executor 都分配到某個(gè)節(jié)點(diǎn)上。

3.4 問(wèn)題5:為什么4次失敗都操作同一個(gè)壞的盤(pán)?

該 NodeManger 實(shí)際上有/mnt/dfs/{0-11}, 一共12塊盤(pán),從物理檢查上看,整個(gè)過(guò)程中也只有/mnt/dfs/4有異常告警,那為啥 Spark 這么傻?這么多好盤(pán)不用,專挑一塊壞的盤(pán)死磕?

我們可以先看下出錯(cuò)的文件,我們包這個(gè)文件分成5個(gè)部分來(lái)看,

1. /mnt/dfs/4/yarn/local/
2. usercache/da_haitao/appcache/application_1568691584183_1953115/ blockmgr-1b6553f2-a564-4b31-a4a6-031f21c9c30f/
3. 0a/
4. shuffle_96_2685_0.index
5. .82594412-1f46-465e-a067-2c5e386a978e
  • 第一行,是 Yarn NodeManger 所配置的LOCAL_DIR的一部分,完整的應(yīng)該包括12塊盤(pán)
  • 第二行,是 Spark 生成的 BlockManger 的根目錄之一,其他盤(pán)符下也有類似的一個(gè)目錄
  • 第三行,是一個(gè)根目錄下的一級(jí)子目錄,數(shù)量由spark.diskStore.subDirectories 默認(rèn)為64控制
  • 第四行,Spark Shuffle 過(guò)程產(chǎn)生的兩個(gè)重要的文件之一,一個(gè)是數(shù)據(jù)文件 .data 結(jié)尾,另一個(gè)就是這個(gè)與之對(duì)應(yīng)的 .index 文件。96是 ShuffleID 表標(biāo)識(shí)是哪個(gè)Shuffle 過(guò)程,2685是 MapID 對(duì)應(yīng)的是 一個(gè)RDD 所以有分區(qū)中其中一個(gè)的順序號(hào), 而0是一個(gè)固定值,原本表示是ReduceID,Spark Sort Based Shuffle 的實(shí)現(xiàn)不需要依賴這個(gè)值,所以被固定為了0。通過(guò)Shuffle ID和 MapId,Shufle Write 階段就可以生成類似shuffle_96_2685_0.index這樣的文件,而Shuffle Read 階段也可以通過(guò)兩個(gè)ID 定位到這個(gè)文件。
  • 第五行, 是Index文件的對(duì)應(yīng)臨時(shí)文件的UUID標(biāo)識(shí)。

基于這樣的邏輯,對(duì)于某次Shuffle 過(guò)程的某個(gè)分區(qū)(Partition)的最終輸出文件名其實(shí)是可以預(yù)測(cè)的也是固定的,比如我們這個(gè) case 中,第96次shuffle的第2685分區(qū)的 index 文件的文件名即為shuffle_96_2685_0.index。

Spark 在寫(xiě)和讀這個(gè)文件的時(shí)候,基于相同的定位邏輯(算法)來(lái)保證依賴關(guān)系,
第一步確定根目錄,Spark 通過(guò)文件名的hash絕對(duì)值與盤(pán)符數(shù)的模,作為索引卻確定根目錄

scala> math.abs("shuffle_96_2685_0.index".hashCode) % 12
res0: Int = 6

而根目錄的數(shù)組對(duì)于一個(gè) Executor 的這個(gè)生命周期內(nèi)而言是確定的,它是一個(gè)由簡(jiǎn)單隨機(jī)算法將所有路徑打散的一個(gè)固定數(shù)組。所以一旦文件名稱確定,Executor 不換的話,根目錄一定是確定的。所以都固定的去訪問(wèn)/mnt/dfs/4這個(gè)壞盤(pán)。
但這只解釋了一個(gè) Executor 所被分配 Task 失敗的原因,我們的 Task 還在不同的 executor 上進(jìn)行過(guò)嘗試。

3.5 問(wèn)題5:為什么兩個(gè) Executor 上的重試都失敗了?

其實(shí)這個(gè)問(wèn)題只是概率的問(wèn)題, Spark 用類似下面算法打亂所有LOCAL_DIRS的配置,如下面的的簡(jiǎn)單測(cè)試,這種碰撞的概率還是極高的,我們ID 5,6,的 Executor 下 DiskBlockManager 包含的 localDirs(6)應(yīng)該都對(duì)應(yīng)于 /mnt/dfs/4 這個(gè)壞盤(pán)。

scala> def randomizeInPlace[T](arr: Array[Int], rand: java.util.Random = new java.util.Random): Array[Int] = {
     |     for (i <- (arr.length - 1) to 1 by -1) {
     |       val j = rand.nextInt(i + 1)
     |       val tmp = arr(j)
     |       arr(j) = arr(i)
     |       arr(i) = tmp
     |     }
     |     arr
     |   }
randomizeInPlace: [T](arr: Array[Int], rand: java.util.Random)Array[Int]
scala> randomizeInPlace(res11)
res23: Array[Int] = Array(3, 2, 4, 1)

scala> randomizeInPlace(res11)
res24: Array[Int] = Array(2, 3, 4, 1)

scala> randomizeInPlace(res11)
res25: Array[Int] = Array(2, 1, 3, 4)

scala> randomizeInPlace(res11)
res26: Array[Int] = Array(4, 2, 1, 3)

scala> randomizeInPlace(res11)
res27: Array[Int] = Array(2, 3, 4, 1)

4. 總結(jié)

4.1 問(wèn)題原因

集群某個(gè)或某幾個(gè)物理機(jī)上某塊或某幾塊盤(pán)出現(xiàn)磁盤(pán)問(wèn)題時(shí),Spark 由于數(shù)據(jù)本地性原因反復(fù)把 Task 調(diào)度到這個(gè)節(jié)點(diǎn)的某個(gè) Executor,或這個(gè)節(jié)點(diǎn)的其他 Executor 上, 前者必然失敗,后者有概率失敗。
當(dāng)然忽略數(shù)據(jù)本地性進(jìn)行隨機(jī)調(diào)度,也有一定的概率出現(xiàn)“現(xiàn)象”為“本地重試”的這種失敗場(chǎng)景,但數(shù)據(jù)本地性的策略會(huì)極大的放大這個(gè)概率。

4.2 規(guī)避方案

  • 設(shè)置 spark.locality.wait=0s,讓 Task 有更大的概率調(diào)度到別的節(jié)點(diǎn),當(dāng)然可能會(huì)影響一定的性能
  • 設(shè)置 spark.blacklist.enabled=true, 開(kāi)啟黑名單,把問(wèn)題節(jié)點(diǎn)加到黑名單中,暫不參與 Task 的分配。當(dāng)然使用黑名單的話,不注意也很容易踩坑。

4.3 解決方案

說(shuō)來(lái)也巧,在我剛?cè)ド鐓^(qū)提https://issues.apache.org/jira/browse/SPARK-29257這個(gè) JIRA,并溝通初步方案時(shí),發(fā)現(xiàn)社區(qū)在兩天之前剛將https://github.com/apache/spark/pull/25620 這個(gè)Pull request合入了,雖然這個(gè)PR不是專門(mén)解決我所提到的這個(gè)問(wèn)題的,但它確產(chǎn)生了一個(gè)副作用,剛好解決了這個(gè)問(wèn)題。
本質(zhì)的想法就是構(gòu)建shuffle_${shuffleId}_${mapId}_0.index 這類Shuffle文件時(shí),可以讓每次重試都可以生成 Unique 的文件名,這樣就可以生成不同的 hash 值并挑選別的盤(pán)作為根目錄了,這樣就不會(huì)一直在一塊壞盤(pán)上吊死。這個(gè)PR中已經(jīng)將mapId換成了每個(gè) task 的 taskAttemtId,而這個(gè)值就是unique的,所以天然就解決了這個(gè)問(wèn)題。
對(duì)于2.x的 Spark 版本,大家可以嘗試合入這個(gè)PR.

5. 參考文獻(xiàn)

https://issues.apache.org/jira/browse/SPARK-29257
https://github.com/apache/spark/pull/25620

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,746評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,991評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,706評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,036評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,203評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,451評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,677評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,857評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,266評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,606評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,407評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,643評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • Apache Spark 是專為大規(guī)模數(shù)據(jù)處理而設(shè)計(jì)的快速通用的計(jì)算引擎。Spark是UC Berkeley AM...
    大佛愛(ài)讀書(shū)閱讀 2,849評(píng)論 0 20
  • spark-submit的時(shí)候如何引入外部jar包 在通過(guò)spark-submit提交任務(wù)時(shí),可以通過(guò)添加配置參數(shù)...
    博弈史密斯閱讀 2,770評(píng)論 1 14
  • 原文:https://tech.meituan.com/spark-tuning-pro.html Spark性能...
    code_solve閱讀 1,202評(píng)論 0 34
  • 1 數(shù)據(jù)傾斜調(diào)優(yōu) 1.1 調(diào)優(yōu)概述 有的時(shí)候,我們可能會(huì)遇到大數(shù)據(jù)計(jì)算中一個(gè)最棘手的問(wèn)題——數(shù)據(jù)傾斜,此時(shí)Spar...
    wisfern閱讀 2,939評(píng)論 0 23
  • “三分技術(shù),七分?jǐn)?shù)據(jù)”,今后得數(shù)據(jù)者得天下。 維克托·邁爾-舍恩伯格在《大數(shù)據(jù)時(shí)代》一書(shū)中舉了百般例證,都是為了說(shuō)...
    雅蜜蜜閱讀 917評(píng)論 0 0