Hive、Inceptor數據傾斜詳解及解決


一、傾斜造成的原因

正常的數據分佈理論上都是傾斜的,就是我們所說的20-80原理:80%的財富集中在20%的人手中, 80%的用戶只使用20%的功能 , 20%的用戶貢獻了80%的訪問量。

俗話是,一個人累死,其他人閒死的局面

這也違背了並行計算的初衷,首先一個節點要承受着巨大的壓力,而其他節點計算完畢後要一直等待這個忙碌的節點,也拖累了整體的計算時間,可以說效率是十分低下的。

下面舉個簡單的例子:

舉個 word count 的入門例子:

它的map 階段就是形成 (“aaa”,1)的形式,然後在reduce 階段進行 value 相加,得出 “aaa” 出現的次數。

若進行 word count 的文本有100G,其中 80G 全部是 “aaa” 剩下 20G 是其餘單詞,那就會形成 80G 的數據量交給一個 reduce 進行相加,其餘 20G 根據 key 不同分散到不同 reduce 進行相加的情況。如此就造成了數據傾斜,臨牀反應就是 reduce 跑到 99%然後一直在原地等着 那80G 的reduce 跑完。

表現如下:

有一個多幾個reduce卡住

各種container報錯OOM

讀寫的數據量極大,至少遠遠超過其它正常的reduce

伴隨着數據傾斜,會出現任務被kill等各種詭異的表現。

二、傾斜原理

原理剖析

在進行shuffle的時候,必須將各個節點上相同的Key拉取到某個節點上的一個task來進行處理,比如按照key進行聚合或者join操作。如果某個key對應的數據量特別大的話,會發生數據傾斜。

三、傾斜解決

  1. 將reduce join 轉爲map join-----一般用於直接sql查詢的場景
什麼是MapJoin?

MapJoin顧名思義,就是在Map階段進行表之間的連接。而不需要進入到Reduce階段才進行連接。這樣就節省了在Shuffle階段時要進行的大量數據傳輸。從而起到了優化作業的作用。

MapJoin的原理:

通常情況下,要連接的各個表裏面的數據會分佈在不同的Map中進行處理。即同一個Key對應的Value可能存在不同的Map中。這樣就必須等到Reduce中去連接。

要使MapJoin能夠順利進行,那就必須滿足這樣的條件:除了一份表的數據分佈在不同的Map中外,其他連接的表的數據必須在每個Map中有完整的拷貝。

MapJoin適用的場景:

通過上面分析你會發現,並不是所有的場景都適合用MapJoin. 它通常會用在如下的一些情景:在二個要連接的表中,有一個很大,有一個很小,這個小表可以存放在內存中而不影響性能。

這樣我們就把小表文件複製到每一個Map任務的本地,再讓Map把文件讀到內存中待用。

MapJoin的實現方法

     1)在Map-Reduce的驅動程序中使用靜態方法DistributedCache.addCacheFile()增加要拷貝的小表文件,。JobTracker在作業啓動之前會獲取這個URI列表,並將相應的文件拷貝到各個TaskTracker的本地磁盤上。

     2)在Map類的setup方法中使用DistributedCache.getLocalCacheFiles()方法獲取文件目錄,並使用標準的文件讀寫API讀取相應的文件。

Hive內置提供的優化機制之一就包括MapJoin。

在Hive v0.7之前,需要使用hint提示 /*+ mapjoin(table) */纔會執行MapJoin  。Hive v0.7之後的版本已經不需要給出MapJoin的指示就進行優化。它是通過如下配置參數來控制的:

hive> set hive.auto.convert.join=true;

否則需要通過sql代碼進行修改

select /*+ mapjoin(A)*/ f.a,f.b from A t join B f  on ( f.a=t.a and f.ftime=20110802)

Hive還提供另外一個參數--表文件的大小作爲開啓和關閉MapJoin的閾值。

hive.mapjoin.smalltable.filesize=25000000 即25M

實現原理:
普通的join是會走shuffle過程的,而一旦shuffle,就相當於會將相同key的數據拉取到一個shuffle read task中再進行join,此時就是reduce join。但是如果一個RDD是比較小的,則可以採用廣播小RDD全量數據+map算子來實現與join同樣的效果,也就是mao join ,而此時不會發生shuffle操作,也就不會發生數據傾斜。
方案優點:
對join操作導致的數據傾斜,效果非常好,因爲根本就不會發生shuffle,也就根本不會發生數據傾斜。
方案缺點:
適用場景較少,因爲這個方案只適用於一個大表和一個小表的情況。畢竟我們需要將小表進行廣播,此時會比較消耗內存資源,driver和每個Executor內存中都會駐留一份小RDD的全量數據。如果我們廣播出去的RDD數據比較大,比如10G以上,那麼就可能發生內存溢出了。因此並不適合兩個都是大表的情況。

    2.提高shuffle操作的並行度

方案使用場景:
若我們必須要面對數據傾斜問題,要這麼使用。
思路:
在對RDD執行shuffle算子時,給shuffle算子傳入一個參數,如reduceByKey(1000),該參數設置了這個shuffle算子執行時shuffle read task 的數量。對於Spark SQL中的shuffle類語句,如 groupBy 、join 等需要設置一個參數,即spark.sql.shuffle.partitions。該參數代表了shuffle read task 的並行度,默認值是200。
原理:
增加shuffle read task 的數量,可以讓原本分配給一個task的多個key分配給多個task,從而讓每個task處理比原來更少的數據。舉例來說,如果原本有5個key,每個key對應10條數據,這5個key都是分配給一個task的,那麼這個task就要處理50條數據。而增加了shuffle read task以後,每個task就分配到一個key,即每個task就處理10條數據,那麼自然每個task的執行時間都會變短了。

實現起來比較簡單,可以有效緩解和減輕數據傾斜的影響。
只是緩解了數據傾斜而已,沒有徹底根除問題,根據實踐經驗來看,其效果有限。

3. 兩階段聚合(局部聚合+全局聚合)

方案使用場景:
對RDD執行reduceByKey等聚合類shuffle算子或者在Spark SQL中使用group by語句進行分組聚合時,比較適用這種方案。
思路:
這個方案的核心實現思路就是進行兩階段聚合。第一次是局部聚合,先給每個key都打上一個隨機數,比如10以內的隨機數,此時原先一樣的key就變成不一樣的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就會變成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着對打上隨機數後的數據,執行reduceByKey等聚合操作,進行局部聚合,那麼局部聚合結果,就會變成了(1_hello, 2) (2_hello, 2)。然後將各個key的前綴給去掉,就會變成(hello,2)(hello,2),再次進行全局聚合操作,就可以得到最終結果了,比如(hello, 4)。

方案優點:
對於聚合類的shuffle操作導致的數據傾斜,效果是非常不錯的。通常都可以解決掉數據傾斜,或者至少是大幅度緩解數據傾斜,將Spark作業的性能提升數倍以上。
方案缺點:
僅僅適用於聚合類的shuffle操作,適用範圍相對較窄。如果是join類的shuffle操作,還得用其他的解決方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章