hive性能優(yōu)化
一冕茅、Map階段的優(yōu)化:
(控制hive任務(wù)中的map數(shù),確定合適的map數(shù)蛹找,以及每個(gè)map處理合適的數(shù)據(jù)量)姨伤。
map個(gè)數(shù)影響因子:
- input目錄中文件總個(gè)數(shù);
- input目錄中每個(gè)文件大杏辜病乍楚;
- 集群設(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)行連接,對(duì)應(yīng)著第一個(gè)MR Job攻锰;表a和b連接的結(jié)果晾嘶,再和c進(jìn)行連接,對(duì)應(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值包含很多空值或是異常值,這種情況可以對(duì)異常值賦一個(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相對(duì)分散沉桌,讀入內(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)記)
對(duì)于以下查詢是不支持使用方法二(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è)方法能解決很多場(chǎng)景下的數(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ù)咖城。
注意:只能對(duì)單個(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 中)苔严,最后完成最終的聚合操作定枷。