Hive性能優化之數據傾斜

       Hive的優化分爲join相關的優化和join無關的優化,實際運用來看,join相關的優化佔了很大的比重,而join相關的優化又分爲mapjoin可以解決的join優化和mapjoin無法解決的join優化。

1 數據傾斜

傾斜來自統計學裏的偏態分佈。簡單來說,就是數據的key分佈嚴重不均勻,造成一部分數據特別多,一部分很少的局面。

2 Hive優化

2.1 一般性優化

2.1.1 select * 優化
儘量不要使用 select * from your_table 這樣的方式,用到哪些列就指定哪些列,如select coll, col2 from your_table 。另外, where 條件中也儘量添加過濾條件,以去掉無關的數據行,從而減少整個 MapReduce 任務中需要處理、分發的數據量。

2.2 join無關的優化

2.2.1 group by 引起的傾斜優化
        group by 引起的傾斜主要是group by 列分佈不均勻導致的,優化很簡單,只需設置下面參數即可:

set hive.map.aggr = true  (map端部分聚合)
set hive.groupby.skewindata = true (有數據傾斜時負載均衡)

        此時Hive在數據傾斜的時候會進行負載均衡,生成的查詢計劃會有兩個MapReduce Job。第一個MapReduce Job中,Map的輸出結果集合會隨機分佈到Reduce中,每個Reduce做部分聚合操作並輸出結果,這樣相同的 group by key有可能被分佈到不同的Reduce中,從而達到負載均衡的目的;第二個MapReduce Job再根據預處理的數據結果按照group by key分佈到Reduce中(這個過程可以保證相同的group by key被分配到同一個Reduce中),最後完成最終的聚合操作。

2.2.2 count distinct優化
       
寫Hive sql要小心使用count distinct,因爲很容易引起性能問題,比如:

select count(distinct user) from table;

         由於必須去重,因此Hive會把Map階段的輸出全部分佈到一個Reduce Task上,這樣很容易引起性能問題,優化如下:

select count(1) from (select user from table group by user) tmp;

        其原理:利用group by 去重,再統計group by的行數目。

2.3 大表join小表的優化
        按年齡查詢某天會員的交易記錄,通常會這麼寫:

select b.age,count(1)
from (select mid,amount,remark from fund_mem_detail where dt='20190101') a
left join (select mid,age from mem) b
on a.mid=b.mid
group by b.age;

         通常會員數量是有限的,而交易記錄會非常大,現實中某些人的交易記錄會非常多,這樣就會造成數據傾斜,對於這種大表join小表問題,可以通過mapjoin方式來優化,只需要添加mapjoin hint即可,如下:

select /*+mapjoin(b)*/ b.age,count(1)
from (select mid,amount,remark from fund_mem_detail where dt='20190101') a
left join (select mid,age from mem) b
on a.mid=b.mid
group by b.age;

        /*mapjoin(b)*/即mapjoin hint,如果需要mapjoin多個標,格式爲/*mapjoin(b,c,d)*/。Hive對mapjoin是默認開啓的,參數爲:

set hive.auto.convert.join=true;

      mapjoin優化是在Map階段進行join,而不是像一般情況在reduce階段按照join列進行分發後再每個reduce任務節點上進行join,不需要分發也就沒有傾斜的問題,相反Hive會將小表全量複製到每個Map任務節點(僅複製sql指定的列),然後每個Map任務節點執行lookup小表即可。
      注意,小表不能太大,否則全量複製得不償失,Hive根據參數hive.auto.convert.join.noconditionaltask.size來確定小表的大小是否滿足條件(默認25M),此參數可以修改,一般最大不能超過1G,否則Map任務所在節點內存會撐爆。

2.4 大表join大表的優化

2.4.1 問題場景
        A表是一個彙總表,彙總賣家買家最近90天交易彙總信息,即對每個賣家最近90天,其每個買家共成交多少單,總金額是多少。A表的字段有:buyer_id、seller_id和pay_cnt_90d。
        B表爲賣家基本信息表,包含賣家的評級,比如:S0、S1、S2、S3。B表字段有:seller_id和s_level。
        現在要獲得每個買家在各個級別賣家的成交比例信息,如:S0:10%、S1:20%、S2:30%、S3:40%,正常可能這麼寫:

select 
       m.buyer_id 
       ,sum(pay_cnt_90d)                                                      as pay_cnt_90d 
       ,sum(case when m.s_level=0 then pay_cnt_90d end) as pay_cnt_90d_s0 
       ,sum(case when m.s_level=1 then pay_cnt_90d end) as pay_cnt_90d_s1
       ,sum(case when m.s_level=2 then pay_cnt_90d end) as pay_cnt_90d_s2 
       ,sum(case when m.s_level=3 then pay_cnt_90d end) as pay_cnt_90d_s3
from 
(
     select 
             a.buyer_id,a.seller_id,b.s_level,a.pay_cnt_90d 
     from (select buyer_id ,seller_id,pay_cnt_90d 
             from table_A ) a 
     join (select seller_id,s_level 
             from table_B ) b 
     on a.seller_id=b.seller_id 
) m 
group by m.buyer_id;

         此sql會引起數據傾斜,因爲某些賣家會有幾百萬甚至千萬的買家,但是大部分賣家的買家數量並不多,join table_A和table_B按照seller_id進行分發,table_A的大賣家引起數據傾斜,但是本數據傾斜無法用mapjoin table_B解決,因爲賣家有超過千萬條,文件大小好幾個G,超過了mapjoin表最大1G的限制。

2.4.2 方案1:轉化爲mapjoin
        儘管B表無法直接mapjoin,但是可以間接的mapjoin它,有兩種途徑:限制行和限制列
        限制行:不需要join B全表,只需要join其在A表中存在的;
        限制列:只取需要的字段。
        加上限制後,如果滿足mapjoin條件,則可以這麼寫:

select 
       m.buyer_id 
       ,sum(pay_cnt_90d)                                                      as pay_cnt_90d 
       ,sum(case when m.s_level=0 then pay_cnt_90d end) as pay_cnt_90d_s0 
       ,sum(case when m.s_level=1 then pay_cnt_90d end) as pay_cnt_90d_s1
       ,sum(case when m.s_level=2 then pay_cnt_90d end) as pay_cnt_90d_s2 
       ,sum(case when m.s_level=3 then pay_cnt_90d end) as pay_cnt_90d_s3
from 
(
     select /*mapjoin(b)*/
             a.buyer_id,a.seller_id,b.s_level,a.pay_cnt_90d 
     from (select buyer_id ,seller_id,pay_cnt_90d 
             from table_A ) a 
     join (select b0.seller_id,b0_s_level 
             from table_B b0
             join (select seller_id from table_A group by seller_id) a0
             on b0.seller_id=a0.seller_id) b 
     on a.seller_id=b.seller_id 
) m 
group by m.buyer_id;

        如果過濾後的B表還是很大,此方案就不起作用了。

2.4.3 方案2:join時用case when語句
        此方案應用的場景爲:傾斜的值是明確的而且數量很少,比如null值引起的傾斜。其核心是將這些引起傾斜的值隨機分發到reduce,主要邏輯在於join時對這些特殊值concat隨機數,從而達到隨機分發的目的

select a.user_id,a.order_id,b.user_id
from table_A a
join table_B b
on (case when a.user_id is null then concat('hive',rand()) else a.user_id end)=b.user_id

        此方案也無法解決問題場景的傾斜問題,因爲傾斜的賣家大量存在且動態變化。 

2.4.4 方案3:倍數B表,再取模
        沒看懂,不解釋

2.4.5 方案4:動態一分爲二
        對於mapjoin不能解決的問題,終極方案就是動態一分爲二,即對傾斜和不傾斜的鍵值分開處理,不傾斜的正常join,傾斜的找出來後做mapjoin,最後union all其結果即可。

--先找出近90天買家數超過10000的賣家
create table if not exists tmp_talbe_B as
select m.seller_id,n.s_level
from (select seller_id from
                   (select seller_id,count(buyer_id) byr_cnt from table_A group by seller_id) a where a.byr_cnt>10000
) m
left join (select seller_id,s_level from table_B) n
on m.seller_id=n.seller_id;

--對於超過買家數超過10000的賣家mapjoin,其它賣家正常join即可
select 
       m.buyer_id 
       ,sum(pay_cnt_90d)                                                      as pay_cnt_90d 
       ,sum(case when m.s_level=0 then pay_cnt_90d end) as pay_cnt_90d_s0 
       ,sum(case when m.s_level=1 then pay_cnt_90d end) as pay_cnt_90d_s1
       ,sum(case when m.s_level=2 then pay_cnt_90d end) as pay_cnt_90d_s2 
       ,sum(case when m.s_level=3 then pay_cnt_90d end) as pay_cnt_90d_s3
from
(
       select a.buyer_id,a.seller_id,b.s_level,a.pay_cnt_90d
       from (select seller_id,buyer_id,pay_cnt_90d from table_A) a
       join
       (
             select a.seller_id,b.s_level
             from table_A a
             left join tmp_table_B b on a.seller_id=b.seller_id
             where b.seller_id is null

       ) b
       on a.seller_id=b.seller_id

       union all

       select /*mapjoin(b)*/
                  a.buyer_id,a.seller_id,b.s_level,a.pay_cnt_90d
       from (select seller_id,buyer_id,pay_cnt_90d from table_A) a
       join (select seller_id,s_level from tmp_table_B ) b
       on a.seller_id=b.seller_id
) m
group by m.buyer_id;

        方案4需要新建一個臨時表存放每日動態變化的大賣家,此方案最通用,自由度最高,可作爲終極方案來使用。

 

set hive.exec.dynamici.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
set mapreduce.map.memory.mb=16000;
set mapreduce.map.java.opts='-Xmx15g';
set hive.map.aggr=true;
set hive.groupby.skewindata=true;

參考文獻:離線和實時大數據開發實踐  朱松嶺著

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