如何解決Hive中經常出現的資料傾斜問題

hd_system發表於2021-02-17

        Hive 執行 MapReduce 過程中經常會出現資料傾斜問題,具體表現為:作業經常在 Reduce 過程完成 99% 的時候一直停留,最後 1% 一直保持很久才完成。這種情況是由於:在資料量很大的情況下,在 MapReduce 的 Shuffle 過程執行後,key值分佈到 Reducer 節點不均勻;有的 key 少,雜湊後被分在不同節點中沒有問題,但是有的 key 特別的多,過於集中了,全被分配在一個 Reducer 節點,所以其他的 Reducer 都執行完了都在等這個量大的 key 值,這就導致了資料傾斜。通俗的話來講就是,一堆幹完活的人等那個乾的最慢的人,不是因為那個人能力差,大家能力水平都相同,是他真的幹不完……被分配太多了,別人做完了也沒法幫忙。
所以這也違背了 MapReduce 方法論產生的核心思路,不怕活兒多,活多我們們可以多分配人手;最怕的就是活分配不均勻,有人乾的多,有人乾的少,出現時間上的浪費。這些工作在 MapReduce 過程中,往往都出現在 GROUP BY 等分組,各種型別 JOIN 過程中。常見產生資料傾斜的原因大致有以下幾種:

  • 大量空值
  • 某個 key 值大量重複
  • 不同資料型別關聯
  • COUNT(DISTINCT)

接下來詳細介紹各種出現以及如何避免這類問題的出現。

二、問題分類及解決思路

1.大量空值產生資料傾斜

解決思路:利用隨機數將空值進行隨機填充,注意:不要讓隨機數碰撞到其他值,提前要測試下是否有膨脹現象發生,之所以選擇以下例子是因為流量表中往往存在無”主鍵”的情況,c端使用者不登入,就不會在流量表記錄使用者唯一值。例如:
最佳化前:

select * from click_log a left join users b on a.user_id = b.user_id;

最佳化方法1. 讓 user_id 為空的不參與關聯,

select *

  from click_log a

  join users b

    on a.user_id is not null

   and a.user_id = b.user_id

union all

select * from click_log a where a.user_id is null;

最佳化方法2. 讓隨機數衝散堆積在一個人 Reduce 中的很多 null 值,

select *

  from click_log a

  left join users b

    on case

         when a.user_id is null then

          concat('dp_hive', rand())

         else

          a.user_id

       end = b.user_id;

2.某 key 值大量重複產生資料傾斜解釋:如果key均為空值,大量的key會分佈在同一個Reduce節點上;在其他Reduce節點完成ReduceTask後,存在大量空值的Reduce還未完成Task,因此產生資料傾斜。 concat('dp_hive',rand())是為了把空值變成一個字串加上隨機數的,把 null值傾斜的資料分佈在不同Reduce節點上,間接把傾斜的資料分佈在不同Reduce上。

其實,當key值非空,但某個key出現大量重複的情況的解決方案和上述空值情況相同,均為引入隨機數進行最佳化。
最佳化前:

select a.key as key, count(b.pv) as pv

  from test_table1 a

 inner join test_table2 b

    on a.key = b.key

 group by 1;

最佳化後:

select a.key as key, b.pv as pv

  from (select key from test_table1) a

 inner join (select key, sum(pv) as pv

               from (select key, round(rand() * 1000) as rnd, count(1) as pv

                       from test_table2

                      group by 1, 2) tmpgroup by 1) b

    on a.key = b.key;

解釋: round(rand()*1000) as rnd –> sum(pv) 加入隨機,將本來ReduceTask在一組的key,拆分成多組進行處理,增加併發度。

3.不同資料型別關聯產生資料傾斜

使用者表user_id欄位為 bigint,click_log 表中user_id欄位既有string型別也有bigint型別。當按照user_id進行兩個表的join操作時,預設的hash操作會按bigint型的id來進行分配,這樣會導致所有string型別id的記錄都分配到一個Reduce中,例如:
最佳化前:

select * from click_log a left join users b on a.user_id = b.user_id;

最佳化後:

select *

  from users a

  left join click_log b

    on a.user_id = cast(b.user_id as string)

4.COUNT(DISTINCT) 產生資料傾斜

首先,說說 group bydistinct 的區別。我在工作過程中實踐過很多場景,發現在處理時間上並沒有什麼本質區別,都是在一個 job 中,出現一個 reduce 過程。那麼為什麼一定要要強呼叫 group by 代替 distinct 呢? 我在網上搜了很多資料,都沒有說清楚這個問題的本質。接下來我來說說我的理解:
其實,大部分情況我們根本不用代替,因為 大部分的情況 資料是不傾斜的
舉個例子,我們經常計算每個使用者這一年變換手機號次數,Email次數等等,當然不會傾斜,因為誰沒事兒老去更換手機號呢!
所以,資料不傾斜的時候基本上兩個過程是等價的。但是,一旦出現資料傾斜,還是要代替一下,因為 group by 覆蓋到了很多 hive 計算引擎引數設定,例如:

  • set hive.groupby.mapaggr.checkinterval = 100000; – 這個是 grby 的鍵對應的記錄條數超過這個值則會進行最佳化
  • set hive.groupby.skewindata = true; – 如果 grby 過程中出現傾斜,設定成 true,會自動將相同的reduceKey進行 Hashing

除此之外, join 也涉及到了一些,如下:

  • set hive.skewjoin.key = 100000; – 這是 join 的鍵對應的記錄條數超過這個值則會進行最佳化;
  • set hive.optimize.skewjoin = true; – 如果是 join 過程中出現資料傾斜,設定成 true

其次count(distinct) 會壓力都積壓在一個 reduce 過程中,導致一個job處理太多資料,導致資料傾斜,改成 count(1) + group by 的處理方式,這樣會將整個過程解耦,進而分解整體過程中的壓力。

最後,有一點自己在寫 Hive Sql 時候的體會,程式碼可讀性和效能最佳化,一定都要考慮,過於複雜的程式碼邏輯一定要加註釋,增強程式碼可讀性,舉個例子:

select a, sum(b), count(distinct c),.. . from T group by a;

最佳化後本應該是:

select a, sum(b) as b, count(c) as c,.. .

  from (select a, b, null as c,.. .

          from T

         group by a, b

        union all

        select a, 0 as b, c,.. . from T group by a, c union all .. .) tmp1

 group by a;

其實,企業級的 hive 在處理 T+1 資料時,處理的速度並不會相差太遠,即 最佳化程度不高的情況下,建議保留業務邏輯

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29209863/viewspace-2757366/,如需轉載,請註明出處,否則將追究法律責任。

相關文章