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_idunion 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;
參考文獻:離線和實時大數據開發實踐 朱松嶺著