本篇文章來介紹一個重量級的Spark調(diào)優(yōu)機制,就是我們常說的shuffle調(diào)優(yōu)。在講解shuffle調(diào)優(yōu)之前,我們先來明確一個概念,什么是shuffle操作?
問題:什么是shuffle?
答案:每個Spark作業(yè)啟動運行的時候,首先Driver進程會將我們編寫的Spark作業(yè)代碼分拆為多個stage,每個stage執(zhí)行一部分代碼片段,并為每個stage創(chuàng)建一批Task,然后將這些Task分配到各個Executor進程中執(zhí)行。一個stage的所有Task都執(zhí)行完畢之后,在各個executor節(jié)點上會產(chǎn)生大量的文件,這些文件會通過IO寫入磁盤(這些文件存放的時候這個stage計算得到的中間結(jié)果),然后Driver就會調(diào)度運行下一個stage。下一個stage的Task的輸入數(shù)據(jù)就是上一個stage輸出的中間結(jié)果。如此循環(huán)往復,直到程序執(zhí)行完畢,最終得到我們想要的結(jié)果。Spark是根據(jù)shuffle類算子來進行stage的劃分。如果我們的代碼中執(zhí)行了某個shuffle類算子(比如groupByKey、countByKey、reduceByKey、join等等)每當遇到這種類型的RDD算子的時候,劃分出一個stage界限來。
每個shuffle的前半部分stage的每個task都會創(chuàng)建出后半部分stage對應的task數(shù)量的文件,(注意是前半部分的每個task都會創(chuàng)建相同數(shù)量的文件)。shuffle的后半部分stage的task拉取前半部分stage中task產(chǎn)生的文件(這里拉取的文件是:屬于自己task計算的那部分文件);然后每個task會有一個內(nèi)存緩沖區(qū),使用HashMap對值進行匯集;比如,task會對我們自己定義的聚合函數(shù),如reduceByKey()算子,把所有的值進行累加,聚合出來得到最終的值,就完成了shuffle操作。
那么默認的這種shuffle操作對性能有什么影響嗎?舉個例子;有100個節(jié)點,每個節(jié)點運行一個executor,每個executor有2個cpu core,總共有1000個task;那么每個executor平均10個task。那么每個節(jié)點將會輸出map端文件為:10 * 1000 = 10000;整個map端輸出的文件數(shù):100 * 10000 = 100萬;shuffle中寫磁盤操作是最消耗性能的。那么有什么辦法可以降低文件個數(shù)的產(chǎn)生呢?先來看看下面這個圖
為了解決產(chǎn)生大量文件的問題,我們可以在map端輸出的位置,將文件進行合并操作,即使用
spark.shuffle.consolidateFiles?參數(shù)來合并文件,具體的使用方式為?
new SparkConf().set("spark.shuffle.consolidateFiles","true")
再看看開啟map端文件合并以后的情況,如下圖所示:
????????從上圖可以看出,開啟文件合并以后,我們map端輸出的文件會變?yōu)?0萬左右,也就是說map端輸出的文件是原來默認的五分之一。所以說通過這個參數(shù)的設置,可以大大提升我們Spark作業(yè)的運行速度。下面我們再來了解一下關于map端內(nèi)存緩沖和reduce端內(nèi)存占比的優(yōu)化。
什么是map端內(nèi)存緩沖區(qū)呢?默認情況下,每個map端的task 輸出的一些中間結(jié)果在寫入磁盤之前,會先被寫入到一個臨時的內(nèi)存緩沖區(qū),這個緩沖區(qū)的默認大小為32kb,當內(nèi)存緩沖區(qū)滿溢之后,才會將產(chǎn)生的中間結(jié)果spill到磁盤上。
reduce端內(nèi)存占比又是什么呢?reduce端的task在拉取到數(shù)據(jù)之后,會用一個hashmap的數(shù)據(jù)結(jié)構(gòu)對各個key對應的value進行匯聚操作。在進行匯聚操作的時候,其使用的內(nèi)存是由executor進程給分配的,默認將executor的內(nèi)存的20%分配給reduce task 進行聚合操作使用。這里會有一個問題,當reduce task拉取的數(shù)據(jù)很多導致其分配的內(nèi)存放不下的時候,這個時候會將放不下的數(shù)據(jù)全部spill到磁盤上去。
為了解決map端數(shù)據(jù)滿溢引發(fā)的spill和reduce端數(shù)據(jù)過大引發(fā)的spill操作。我們可以通過兩個參數(shù)來適當調(diào)整,以避免上述情況的出現(xiàn),這個兩個參數(shù)分別是:
spark.shuffle.file.buffer ? ? ? ? ? ? ? ? ? ? map task的內(nèi)存緩沖調(diào)節(jié)參數(shù),默認是32kb
spark.shuffle.memoryFraction ? ? ? ? ?reduce端聚合內(nèi)存占比,默認0.2
怎么判斷在什么時候?qū)@兩個參數(shù)進行調(diào)整呢?
通過監(jiān)控平臺查看每個executor的task的shuffle write和shuffle read的運行次數(shù),如果發(fā)現(xiàn)這個指標的運行次數(shù)比較多,那么就應該考慮這兩個參數(shù)的調(diào)整了;這個參數(shù)調(diào)整有一個前提,spark.shuffle.file.buffer參數(shù)每次擴大一倍的方式進行調(diào)整,spark.shuffle.memoryFraction參數(shù)每次增加0.1進行調(diào)整。
總結(jié):本文主要介紹三個關于shuffle調(diào)優(yōu)的參數(shù),分別為?spark.shuffle.consolidateFiles,spark.shuffle.file.buffer,spark.shuffle.memoryFraction。請大家根據(jù)自己的情況進行相關參數(shù)的調(diào)整。好了,本文到這里差不多就結(jié)束了,后續(xù)還會不斷更新關于Spark作業(yè)優(yōu)化的一些其他方式,歡迎關注。
如需轉(zhuǎn)載,請注明: