Hive的性能優(yōu)化以及數(shù)據(jù)傾斜

hive性能優(yōu)化

一、Map階段的優(yōu)化:

(控制hive任務(wù)中的map數(shù),確定合適的map數(shù),以及每個(gè)map處理合適的數(shù)據(jù)量)

map個(gè)數(shù)影響因子:

  1. input目錄中文件總個(gè)數(shù);
  2. input目錄中每個(gè)文件大??;
  3. 集群設(shè)置的文件塊大小(默認(rèn)為128M, 可在hive中通過set dfs.block.size;命令查看,不能在hive中自定義修改);
舉例:
input目錄中有1個(gè)文件(300M),會(huì)產(chǎn)生3個(gè)塊(2個(gè)128M,1個(gè)44M)即3個(gè)Map數(shù)。
input目錄中有3個(gè)文件(5M,10M,200M),會(huì)產(chǎn)生4個(gè)塊(5M,10M,128M,72M)即4個(gè)Map數(shù)。
適當(dāng)減少M(fèi)ap數(shù):

當(dāng)一個(gè)任務(wù)有很多小文件(遠(yuǎn)遠(yuǎn)小于塊大小128m),會(huì)產(chǎn)生很多Map,而一個(gè)Map任務(wù)啟動(dòng)和初始化的時(shí)間遠(yuǎn)遠(yuǎn)大于邏輯處理的時(shí)間,就會(huì)造成很大的資源浪費(fèi),而且同時(shí)可執(zhí)行的map數(shù)是受限的。

set mapred.max.split.size=100000000;//(100M)
set mapred.min.split.size.per.node=100000000;
set mapred.min.split.size.per.rack=100000000;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;//表示執(zhí)行前進(jìn)行小文件合并。
//大于128:按照128M分割;100~128按照100分;小于100的進(jìn)行合并。
適當(dāng)增加Map數(shù):

當(dāng)有一個(gè)小于128M的文件(其中有上千萬的數(shù)據(jù),字段少并且數(shù)據(jù)單位小),如果map處理的邏輯比較復(fù)雜,用一個(gè)map任務(wù)去做,耗時(shí)比較大。

set mapred.reduce.tasks=10;
create table a_1 as 
select * from a distribute by rand();
//表示通過設(shè)置Map任務(wù)數(shù)來中加Map,把a(bǔ)表中的數(shù)據(jù)均勻的放到a_1目錄下10個(gè)文件中。
Map端聚合:
set  hive.map.aggr=true ;(默認(rèn)為true)

二、Reduce階段的優(yōu)化:

2.1 指定reduce數(shù)量
 set mapred.reduce.tasks=10
2.2未指定reduce數(shù)量
param1:hive.exec.reducers.bytes.per.reducer(默認(rèn)為1000^3)
param2:hive.exec.reducers.max(默認(rèn)為999)
reduceNum = min(param2,總輸入數(shù)據(jù)量/param1(reduceNum = InputFileSize / bytes per reducer))

通常情況下,有必要手動(dòng)指定reducer個(gè)數(shù)??紤]到map階段的輸出數(shù)據(jù)量通常會(huì)比輸入有大幅減少,因此即使不設(shè)定reducer個(gè)數(shù),重設(shè)參數(shù)2還是必要的。依據(jù)Hadoop的經(jīng)驗(yàn),可以將參數(shù)2設(shè)定為0.95*(集群中TaskTracker個(gè)數(shù))。

三、其他優(yōu)化:

Multi-insert & multi-group by

從一份基礎(chǔ)表中按照不同的維度,一次組合出不同的數(shù)據(jù)

FROM from_statement
INSERT OVERWRITE TABLE tablename1 [PARTITION (partcol1=val1)] select_statement1 group by key1
INSERT OVERWRITE TABLE tablename2 [PARTITION(partcol2=val2 )] select_statement2 group by key2
#具體實(shí)例
FROM pv_users 
INSERT OVERWRITE TABLE pv_gender_sum
SELECT pv_users.gender, count(DISTINCT pv_users.userid) 
GROUP BY pv_users.gender 
INSERT OVERWRITE DIRECTORY '/opt/data/users/pv_age_sum'
SELECT pv_users.age, count(DISTINCT pv_users.userid) 
GROUP BY pv_users.age; 
生成MR Job 個(gè)數(shù)
生成一個(gè)MR Job

多表連接,如果多個(gè)表中每個(gè)表都使用同一個(gè)列進(jìn)行連接(出現(xiàn)在JOIN子句中),則只會(huì)生成一個(gè)MR Job。

SELECT a.val, b.val, c.val FROM a 
JOIN b ON (a.key = b.key1) 
JOIN c ON (c.key = b.key1)

三個(gè)表a、b、c都分別使用了同一個(gè)字段進(jìn)行連接,亦即同一個(gè)字段同時(shí)出現(xiàn)在兩個(gè)JOIN子句中,從而只生成一個(gè)MR Job。

生成多個(gè)MR Job

多表連接,如果多表中,其中存在一個(gè)表使用了至少2個(gè)字段進(jìn)行連接(同一個(gè)表的至少2個(gè)列出現(xiàn)在JOIN子句中),則會(huì)至少生成2個(gè)MR Job。

SELECT a.val, b.val, c.val FROM a 
JOIN b ON (a.key = b.key1) 
JOIN c ON (c.key = b.key2)

三個(gè)表基于2個(gè)字段進(jìn)行連接,這兩個(gè)字段b.key1和b.key2同時(shí)出現(xiàn)在b表中。連接的過程是這樣的:首先a和b表基于a.key和b.key1進(jìn)行連接,對應(yīng)著第一個(gè)MR Job;表a和b連接的結(jié)果,再和c進(jìn)行連接,對應(yīng)著第二個(gè)MR Job。

數(shù)據(jù)傾斜:

傾斜原因:

map輸出數(shù)據(jù)按key Hash的分配到reduce中,由于key分布不均勻業(yè)務(wù)數(shù)據(jù)本身的特性、建表時(shí)考慮不周某些SQL語句本身就有數(shù)據(jù)傾斜等原因造成的reduce上的數(shù)據(jù)量差異過大,所以如何將數(shù)據(jù)均勻的分配到各個(gè)reduce中,就是解決數(shù)據(jù)傾斜的根本所在。

解決方案:

1. 空值數(shù)據(jù)傾斜

join的key值發(fā)生傾斜,key值包含很多空值或是異常值,這種情況可以對異常值賦一個(gè)隨機(jī)值來分散key。
案例:在日志中,常會(huì)有信息丟失的問題,比如日志中的 user_id,如果取其中的 user_id 和 用戶表中的user_id 關(guān)聯(lián),會(huì)碰到數(shù)據(jù)傾斜的問題。

select * from log l
left outer join user u on 
case when (l.user_id is null or I.user_id='-' or I.user_id='0') 
then concat(‘sql_hive’,rand() ) else l.user_id end = u.user_id;
2. Join操作產(chǎn)生數(shù)據(jù)傾斜
2.1 大表和小表Join

產(chǎn)生原因:Hive在進(jìn)行join時(shí),按照join的key進(jìn)行分發(fā),而在join左邊的表的數(shù)據(jù)會(huì)首先讀入內(nèi)存,如果左邊表的key相對分散,讀入內(nèi)存的數(shù)據(jù)會(huì)比較小,join任務(wù)執(zhí)行會(huì)比較快;而如果左邊的表key比較集中,而這張表的數(shù)據(jù)量很大,那么數(shù)據(jù)傾斜就會(huì)比較嚴(yán)重,而如果這張表是小表,則還是應(yīng)該把這張表放在join左邊。
解決方式:使用map join讓小的維度表先進(jìn)內(nèi)存。在map端完成reduce。
在0.7.0版本之前:需要在sql中使用 /*+ MAPJOIN(smallTable) */ ;

SELECT /*+ MAPJOIN(b) */ a.key, a.value
FROM a
JOIN b ON a.key = b.key;

在0.7.0版本之后:可以配置hive.auto.convert.join。

配置項(xiàng) 缺省值 配置說明
hive.auto.convert.join (0.7.0-0.10.0)false; (0.11.0-)true 注意:hive-default.xml模板中錯(cuò)誤地將默認(rèn)設(shè)置為false,在Hive 0.11.0到0.13.1
hive.smalltable.filesize(0.7.0) or hive.mapjoin.smalltable.filesize(0.8.1) 25000000 默認(rèn)值為2500000(25M),通過配置該屬性來確定使用該優(yōu)化的表的大小,如果表的大小小于此值就會(huì)被加載進(jìn)內(nèi)存中

注意:使用默認(rèn)啟動(dòng)該優(yōu)化的方式如果出現(xiàn)默名奇妙的BUG(比如MAPJOIN并不起作用),就將以下兩個(gè)屬性置為fase手動(dòng)使用MAPJOIN標(biāo)記來啟動(dòng)該優(yōu)化

hive.auto.convert.join=false(關(guān)閉自動(dòng)MAPJOIN轉(zhuǎn)換操作)
hive.ignore.mapjoin.hint=false(不忽略MAPJOIN標(biāo)記)

對于以下查詢是不支持使用方法二(MAPJOIN標(biāo)記)來啟動(dòng)該優(yōu)化的

select /*+MAPJOIN(smallTableTwo)*/ idOne, idTwo, value FROM
  ( select /*+MAPJOIN(smallTableOne)*/ idOne, idTwo, value FROM
    bigTable JOIN smallTableOne on (bigTable.idOne = smallTableOne.idOne)                                                  
  ) firstjoin                                                            
  JOIN                                                                 
  smallTableTwo ON (firstjoin.idTwo = smallTableTwo.idTwo)  

但是,如果使用的是方法一即沒有MAPJOIN標(biāo)記則以上查詢語句將會(huì)被作為兩個(gè)MJ執(zhí)行,進(jìn)一步的,如果預(yù)先知道表大小是能夠被加載進(jìn)內(nèi)存的,則可以通過以下屬性來將兩個(gè)MJ合并成一個(gè)MJ

hive.auto.convert.join.noconditionaltask:Hive在基于輸入文件大小的前提下將普通JOIN轉(zhuǎn)換成MapJoin,
并是否將多個(gè)MJ合并成一個(gè)
hive.auto.convert.join.noconditionaltask.size:
多個(gè)MJ合并成一個(gè)MJ時(shí),其表的總的大小須小于該值,同時(shí)hive.auto.convert.join.noconditionaltask必須為true
2.2 大表和大表Join

產(chǎn)生原因:業(yè)務(wù)數(shù)據(jù)本身的特性,導(dǎo)致兩個(gè)表都是大表。
解決方式:業(yè)務(wù)削減。
案例:user 表有 500w+ 的記錄,把 user 分發(fā)到所有的 map 上也是個(gè)不小的開銷,而且 map join 不支持這么大的小表。如果用普通的 join,又會(huì)碰到數(shù)據(jù)傾斜的問題。

select * from log l left outer join user u
 on l.user_id = u.user_id;

解決方法:當(dāng)天登陸的用戶其實(shí)很少,先只查詢當(dāng)天登錄的用戶,log里user_id有上百萬個(gè),這就又回到原來map join問題。所幸,每日的會(huì)員uv不會(huì)太多,有交易的會(huì)員不會(huì)太多,有點(diǎn)擊的會(huì)員不會(huì)太多,有傭金的會(huì)員不會(huì)太多等等。所以這個(gè)方法能解決很多場景下的數(shù)據(jù)傾斜問題。

select /*+mapjoin(u2)*/* from log l2
left outer join
 (
select  /*+mapjoin(l1)*/u1.*
from ( select distinct user_id from log ) l1
join user u1 on l1.user_id = u1.user_id
) u2
on l2.user_id = u2.user_id;
3. count distinct 聚 合 時(shí) 存 在 大 量 特 殊 值

產(chǎn)生原因: 做count distinct時(shí),該字段存在大量值為NULL或空的記錄。
解決方式: 做count distinct時(shí),將值為空的情況單獨(dú)處理,如果是計(jì)算count distinct,可以不用處理,直接過濾,在最后結(jié)果中加1。如果還有其他計(jì)算,需要進(jìn)行g(shù)roup by,可以先將值為空的記錄單獨(dú)處理,再和其他計(jì)算結(jié)果進(jìn)行union。
案例
1.只計(jì)算count distinct

select cast(count(distinct user_id)+1 as bigint) as user_cnt
from user
where user_id is not null and user_id <> '';

2.計(jì)算完count distinct 后面還有 group by。同一個(gè)reduce上進(jìn)行distinct操作時(shí)壓力很大,先將值為空的記錄單獨(dú)處理,再和其他計(jì)算結(jié)果進(jìn)行union。
在Hive中,經(jīng)常遇到count(distinct)操作,這樣會(huì)導(dǎo)致最終只有一個(gè)reduce,我們可以先group 再在外面包一層count,就可以了。

select day,
count(case when type='session' then 1 else null end) as session_cnt,
count(case when type='user' then 1 else null end) as user_cnt
from (
  select day,type
  from (
    select day,session_id,'session' as type from log
    union all
    select day user_id,'user' as type from log
  )
  group by day,type
)t1 
group by day;
4. group by 產(chǎn)生傾斜的問題
set hive.map.aggr=true

開啟map端combiner:在Map端做combine,若map各條數(shù)據(jù)基本上不一樣, 聚合無意義,通過如下參數(shù)設(shè)置。

hive.groupby.mapaggr.checkinterval = 100000 (默認(rèn))
hive.map.aggr.hash.min.reduction=0.5(默認(rèn))

解釋:預(yù)先取100000條數(shù)據(jù)聚合,如果聚合后的條數(shù)小于100000*0.5,則不再聚合。

set hive.groupby.skewindata=true;//決定  group by 操作是否支持傾斜數(shù)據(jù)。

注意:只能對單個(gè)字段聚合。
控制生成兩個(gè)MR Job,第一個(gè)MR Job Map的輸出結(jié)果隨機(jī)分配到reduce中減少某些key值條數(shù)過多某些key條數(shù)過小造成的數(shù)據(jù)傾斜問題。
在第一個(gè) MapReduce 中,map 的輸出結(jié)果集合會(huì)隨機(jī)分布到 reduce 中, 每個(gè)reduce 做部分聚合操作,并輸出結(jié)果。這樣處理的結(jié)果是,相同的 Group By Key 有可能分發(fā)到不同的reduce中,從而達(dá)到負(fù)載均衡的目的;
第二個(gè) MapReduce 任務(wù)再根據(jù)預(yù)處理的數(shù)據(jù)結(jié)果按照 Group By Key 分布到 reduce 中(這個(gè)過程可以保證相同的 Group By Key 分布到同一個(gè) reduce 中),最后完成最終的聚合操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容