寫在前面
本文面向的讀者是從事資料分析、資料處理(ETL)等相關工作的朋友們,相信大家在工作中一定遇到過資料傾斜的問題,讀完本文,你會了解到資料傾斜的定義及其危害、產生的原因及應對措施、常見傾斜場景及解決辦法等知識,相信對你今後處理資料傾斜問題會有一定的幫助。
❖
目前流行的大資料相關的計算框架之所以能夠處理大量的資料和計算,基本上都是依賴分散式計算的思想,即由一個透過某種組織關係連線在一起的叢集來共同完成計算任務。
這是一個非常好的計算模型,無論多大的資料量,只要叢集可以擴充套件,就能夠擴充算力,自如應對,但與此同時,也為資料傾斜的產生埋下了伏筆。
什麼是資料傾斜
前面提到分散式計算,是一個叢集共同承擔計算任務,理想狀態下,每個計算節點應該承擔相近資料量的計算任務,然而實際情況通常不會這麼理想,資料分配嚴重不均就會產生資料傾斜。我們先來給資料傾斜下個明確點的定義。
資料傾斜,指的是並行處理的過程中,某些分割槽或節點處理的資料,顯著高於其他分割槽或節點,導致這部分的資料處理任務比其他任務要大很多,從而成為這個階段執行最慢的部分,進而成為整個作業執行的瓶頸,甚至直接導致作業失敗。
舉個實際發生的例子說明下,一個spark作業,其中有個stage是由200個partition組成,在實際執行中,有198個partition在10秒內就完成了,但是有兩個partition執行了3分鐘都沒有完成,並且在執行5分鐘後失敗了。這便是典型的資料傾斜場景,透過觀察SparkUI發現這兩個partition要處理的資料是其他partition的30多倍,屬於比較嚴重的資料傾斜。
資料傾斜的危害
知道了什麼是資料傾斜,那麼它到底有什麼危害,讓大家這麼痛恨它的同時,又很畏懼它呢。
資料傾斜主要有三點危害:
1.任務長時間掛起,資源利用率下降
計算作業通常是分階段進行的,階段與階段之間通常存在資料上的依賴關係,也就是說後一階段需要等前一階段執行完才能開始。
舉個例子,Stage1在Stage0之後執行,假如Stage1依賴Stage0產生的資料結果,那麼Stage1必須等待Stage0執行完成後才能開始,如果這時Stage0因為資料傾斜問題,導致任務執行時長過長,或者直接掛起,那麼Stage1將一直處於等待狀態,整個作業也就一直掛起。這個時候,資源被這個作業佔據,但是卻只有極少數task在執行,造成計算資源的嚴重浪費,利用率下降。
2.引發記憶體溢位,導致任務失敗
資料發生傾斜時,可能導致大量資料集中在少數幾個節點上,在計算執行中由於要處理的資料超出了單個節點的能力範圍,最終導致記憶體被撐爆,報OOM異常,直接導致任務失敗。
3.作業執行時間超出預期,導致後續依賴資料結果的作業出錯
有時候作業與作業之間,並沒有構建強依賴關係,而是透過執行時間的前後時間差來排程,當前置作業未在預期時間範圍內完成執行,那麼當後續作業啟動時便無法讀取到其所需要的最新資料,從而導致連續出錯。
可以看出,資料傾斜問題,就像是一個隱藏的殺手,潛伏在資料處理與分析的過程中,只要一出手,非死即傷。那麼它又是如何產生的呢?想要解決它,我們就要先了解它。
為什麼會產生資料傾斜
1.讀入資料來源時就是傾斜的
讀入資料是計算任務的開始,但是往往這個階段就可能已經開始出現問題了。
對於一些本身就可能傾斜的資料來源,在讀入階段就可能出現個別partition執行時長過長或直接失敗,如讀取id分佈跨度較大的mysql資料、partition分配不均的kafka資料或不可分割的壓縮檔案。
這些場景下,資料在讀取階段或者讀取後的第一個計算階段,就會容易執行過慢或報錯。
2.shuffle產生傾斜
在shuffle階段造成傾斜,在實際的工作中更加常見,比如特定key值數量過多,導致join發生時,大量資料湧向一個節點,導致資料嚴重傾斜,個別節點的讀寫壓力是其他節點的好幾倍,容易引發OOM錯誤。
3.過濾導致傾斜
有些場景下,資料原本是均衡的,但是由於進行了一系列的資料剔除操作,可能在過濾掉大量資料後,造成資料的傾斜。
例如,大部分節點都被過濾掉了很多資料,只剩下少量資料,但是個別節點的資料被過濾掉的很少,保留著大部分的資料。這種情況下,一般不會OOM,但是傾斜的資料可能會隨著計算逐漸累積,最終引發問題。
怎麼預防或解決資料傾斜
1.儘量保證資料來源是均衡的
程式讀入的資料來源通常是上個階段其他作業產生的,那麼我們在上個階段作業生成資料時,就要注意這個問題,儘量不要給下游作業埋坑。
如果所有作業都注意到並謹慎處理了這個問題,那麼出現讀入時傾斜的可能性會大大降低。
這個有個小建議,在程式輸出寫檔案時,儘量不要用coalesce,而是用repartition,這樣寫出的資料,各檔案大小往往是均衡的。
2.對大資料集做過濾,結束後做repartition
對比較大的資料集做完過濾後,如果過濾掉了絕大部分資料,在進行下一步操作前,最好可以做一次repartition,讓資料重回均勻分佈的狀態,否則失衡的資料集,在進行後續計算時,可能會逐漸累積傾斜的狀態,容易產生錯誤。
3.對小表進行廣播
如果兩個資料量差異較大的表做join時,發生資料傾斜的常見解決方法,是將小表廣播到每個節點去,這樣就可以實現map端join,從而省掉shuffle,避免了大量資料在個別節點上的匯聚,執行效率也大大提升。
4.編碼時要注意,不要人為造成傾斜
在寫程式碼時,也要多加註意不要使用容易出問題的運算元,如上文提到的coalesce。
另外,也要注意不要人為造成傾斜,如作者一次在幫別人排查傾斜問題時發現,他在程式碼中使用開窗函式,其中寫到over (partition by 1),這樣就把所有資料分配到一個分割槽內,人為造成了傾斜。
5.join前最佳化
個別場景下,兩個表join,某些特殊key值可能很多,很容易產生資料傾斜,這時可以根據實際計算進行join前最佳化。
如計算是先join後根據key聚合,那可以改為先根據key聚合然後再join。又如,需求是join後做distinct操作,在不影響結果的前提下,可以改為先distinct,然後再join。這些措施都是可以有效避免重複key過多導致join時傾斜。
6.具體問題具體分析
某些具體問題或者解決方案,不具備普遍性,但是也可以作為一種思路參考。
例如,讀入mysql資料時傾斜,這通常是由於mysql的id分佈嚴重不均,中間存在跨度很大的區間造成的。解決方法有兩種,一是加大讀取時的分割槽數,將傾斜的區間劃分開;另一種是,先把id取出來進行等寬切割,確保每個區段的id數量一致,之後再對各區間進行資料讀取。
本文介紹了什麼是資料傾斜、它的危害、產生的原因及一些常用的解決方案,希望可以幫助大家,加深對資料傾斜的認識,如果遇到類似問題,可以快速上手解決掉。