Hive/HiveQL常用優(yōu)化方法全面總結(jié)(下篇)

本文接上篇(http://www.reibang.com/p/8e2f2f0d4b6c)繼續(xù)講解Hive/HiveQL常用優(yōu)化方法,按照目錄国旷,會(huì)從“優(yōu)化SQL處理join數(shù)據(jù)傾斜”說起。

優(yōu)化SQL處理join數(shù)據(jù)傾斜

上篇已經(jīng)多次提到了數(shù)據(jù)傾斜变隔,包括已經(jīng)寫過的sort by代替order by提佣,以及group by代替distinct方法,本質(zhì)上也是為了解決它轿偎。join操作更是數(shù)據(jù)傾斜的重災(zāi)區(qū),需要多加注意被廓。

空值或無意義值

這種情況很常見坏晦,比如當(dāng)事實(shí)表是日志類數(shù)據(jù)時(shí),往往會(huì)有一些項(xiàng)沒有記錄到嫁乘,我們視情況會(huì)將它置為null昆婿,或者空字符串、-1等蜓斧。如果缺失的項(xiàng)很多仓蛆,在做join時(shí)這些空值就會(huì)非常集中,拖累進(jìn)度挎春。
因此看疙,若不需要空值數(shù)據(jù),就提前寫where語句過濾掉搂蜓。需要保留的話狼荞,將空值key用隨機(jī)方式打散辽装,例如將用戶ID為null的記錄隨機(jī)改為負(fù)值:

select a.uid,a.event_type,b.nickname,b.age
from (
  select 
  (case when uid is null then cast(rand()*-10240 as int) else uid end) as uid,
  event_type from calendar_record_log
  where pt_date >= 20190201
) a left outer join (
  select uid,nickname,age from user_info where status = 4
) b on a.uid = b.uid;

單獨(dú)處理傾斜key

這其實(shí)是上面處理空值方法的拓展帮碰,不過傾斜的key變成了有意義的。一般來講傾斜的key都很少拾积,我們可以將它們抽樣出來殉挽,對(duì)應(yīng)的行單獨(dú)存入臨時(shí)表中,然后打上一個(gè)較小的隨機(jī)數(shù)前綴(比如0~9)拓巧,最后再進(jìn)行聚合斯碌。SQL語句與上面的相仿,不再贅述肛度。

不同數(shù)據(jù)類型

這種情況不太常見傻唾,主要出現(xiàn)在相同業(yè)務(wù)含義的列發(fā)生過邏輯上的變化時(shí)。
舉個(gè)例子,假如我們有一舊一新兩張日歷記錄表冠骄,舊表的記錄類型字段是(event_type int)伪煤,新表的是(event_type string)。為了兼容舊版記錄凛辣,新表的event_type也會(huì)以字符串形式存儲(chǔ)舊版的值抱既,比如'17'。當(dāng)這兩張表join時(shí)扁誓,經(jīng)常要耗費(fèi)很長時(shí)間防泵。其原因就是如果不轉(zhuǎn)換類型,計(jì)算key的hash值時(shí)默認(rèn)是以int型做的蝗敢,這就導(dǎo)致所有“真正的”string型key都分配到一個(gè)reducer上捷泞。所以要注意類型轉(zhuǎn)換:

select a.uid,a.event_type,b.record_data
from calendar_record_log a
left outer join (
  select uid,event_type from calendar_record_log_2
  where pt_date = 20190228
) b on a.uid = b.uid and b.event_type = cast(a.event_type as string)
where a.pt_date = 20190228;

build table過大

有時(shí),build table會(huì)大到無法直接使用map join的地步前普,比如全量用戶維度表肚邢,而使用普通join又有數(shù)據(jù)分布不均的問題。這時(shí)就要充分利用probe table的限制條件拭卿,削減build table的數(shù)據(jù)量骡湖,再使用map join解決。代價(jià)就是需要進(jìn)行兩次join峻厚。舉個(gè)例子:

select /*+mapjoin(b)*/ a.uid,a.event_type,b.status,b.extra_info
from calendar_record_log a
left outer join (
  select /*+mapjoin(s)*/ t.uid,t.status,t.extra_info
  from (select distinct uid from calendar_record_log where pt_date = 20190228) s
  inner join user_info t on s.uid = t.uid
) b on a.uid = b.uid
where a.pt_date = 20190228;

MapReduce優(yōu)化

經(jīng)典MR流程响蕴,不再贅述

調(diào)整mapper數(shù)

mapper數(shù)量與輸入文件的split數(shù)息息相關(guān),在Hadoop源碼org.apache.hadoop.mapreduce.lib.input.FileInputFormat類中可以看到split劃分的具體邏輯惠桃。這里不貼代碼浦夷,直接敘述mapper數(shù)是如何確定的。

  • 可以直接通過參數(shù)mapred.map.tasks(默認(rèn)值2)來設(shè)定mapper數(shù)的期望值辜王,但它不一定會(huì)生效劈狐,下面會(huì)提到。
  • 設(shè)輸入文件的總大小為total_input_size呐馆。HDFS中肥缔,一個(gè)塊的大小由參數(shù)dfs.block.size指定,默認(rèn)值64MB或128MB汹来。在默認(rèn)情況下续膳,mapper數(shù)就是:
    default_mapper_num = total_input_size / dfs.block.size
  • 參數(shù)mapred.min.split.size(默認(rèn)值1B)和mapred.max.split.size(默認(rèn)值64MB)分別用來指定split的最小和最大大小收班。split大小和split數(shù)計(jì)算規(guī)則是:
    split_size = MAX(mapred.min.split.size, MIN(mapred.max.split.size, dfs.block.size))坟岔;
    split_num = total_input_size / split_size
  • 得出mapper數(shù):
    mapper_num = MIN(split_num, MAX(default_num, mapred.map.tasks))摔桦。

可見社付,如果想減少mapper數(shù),就適當(dāng)調(diào)高mapred.min.split.size,split數(shù)就減少了鸥咖。如果想增大mapper數(shù)纪隙,除了降低mapred.min.split.size之外,也可以調(diào)高mapred.map.tasks扛或。
一般來講绵咱,如果輸入文件是少量大文件,就減少mapper數(shù)熙兔;如果輸入文件是大量非小文件悲伶,就增大mapper數(shù);至于大量小文件的情況住涉,得參考下面“合并小文件”一節(jié)的方法處理麸锉。

調(diào)整reducer數(shù)

reducer數(shù)量的確定方法比mapper簡(jiǎn)單得多。使用參數(shù)mapred.reduce.tasks可以直接設(shè)定reducer數(shù)量舆声,不像mapper一樣是期望值花沉。但如果不設(shè)這個(gè)參數(shù)的話,Hive就會(huì)自行推測(cè)媳握,邏輯如下:

  • 參數(shù)hive.exec.reducers.bytes.per.reducer用來設(shè)定每個(gè)reducer能夠處理的最大數(shù)據(jù)量碱屁,默認(rèn)值1G(1.2版本之前)或256M(1.2版本之后)。
  • 參數(shù)hive.exec.reducers.max用來設(shè)定每個(gè)job的最大reducer數(shù)量蛾找,默認(rèn)值999(1.2版本之前)或1009(1.2版本之后)娩脾。
  • 得出reducer數(shù):
    reducer_num = MIN(total_input_size / reducers.bytes.per.reducer, reducers.max)

reducer數(shù)量與輸出文件的數(shù)量相關(guān)打毛。如果reducer數(shù)太多柿赊,會(huì)產(chǎn)生大量小文件,對(duì)HDFS造成壓力幻枉。如果reducer數(shù)太少碰声,每個(gè)reducer要處理很多數(shù)據(jù),容易拖慢運(yùn)行時(shí)間或者造成OOM熬甫。

合并小文件

  • 輸入階段合并
    需要更改Hive的輸入文件格式胰挑,即參數(shù)hive.input.format,默認(rèn)值是org.apache.hadoop.hive.ql.io.HiveInputFormat罗珍,我們改成org.apache.hadoop.hive.ql.io.CombineHiveInputFormat洽腺。
    這樣比起上面調(diào)整mapper數(shù)時(shí)脚粟,又會(huì)多出兩個(gè)參數(shù)覆旱,分別是mapred.min.split.size.per.nodemapred.min.split.size.per.rack,含義是單節(jié)點(diǎn)和單機(jī)架上的最小split大小核无。如果發(fā)現(xiàn)有split大小小于這兩個(gè)值(默認(rèn)都是100MB)扣唱,則會(huì)進(jìn)行合并。具體邏輯可以參看Hive源碼中的對(duì)應(yīng)類。
  • 輸出階段合并
    直接將hive.merge.mapfileshive.merge.mapredfiles都設(shè)為true即可噪沙,前者表示將map-only任務(wù)的輸出合并炼彪,后者表示將map-reduce任務(wù)的輸出合并。
    另外正歼,hive.merge.size.per.task可以指定每個(gè)task輸出后合并文件大小的期望值辐马,hive.merge.size.smallfiles.avgsize可以指定所有輸出文件大小的均值閾值,默認(rèn)值都是1GB局义。如果平均大小不足的話喜爷,就會(huì)另外啟動(dòng)一個(gè)任務(wù)來進(jìn)行合并。

啟用壓縮

壓縮job的中間結(jié)果數(shù)據(jù)和輸出數(shù)據(jù)萄唇,可以用少量CPU時(shí)間節(jié)省很多空間檩帐。壓縮方式一般選擇Snappy,效率最高另萤。
要啟用中間壓縮湃密,需要設(shè)定hive.exec.compress.intermediate為true,同時(shí)指定壓縮方式hive.intermediate.compression.codecorg.apache.hadoop.io.compress.SnappyCodec四敞。另外泛源,參數(shù)hive.intermediate.compression.type可以選擇對(duì)塊(BLOCK)還是記錄(RECORD)壓縮,BLOCK的壓縮率比較高忿危。
輸出壓縮的配置基本相同俩由,打開hive.exec.compress.output即可。

JVM重用

在MR job中癌蚁,默認(rèn)是每執(zhí)行一個(gè)task就啟動(dòng)一個(gè)JVM幻梯。如果task非常小而碎,那么JVM啟動(dòng)和關(guān)閉的耗時(shí)就會(huì)很長努释〉馍遥可以通過調(diào)節(jié)參數(shù)mapred.job.reuse.jvm.num.tasks來重用。例如將這個(gè)參數(shù)設(shè)成5伐蒂,那么就代表同一個(gè)MR job中順序執(zhí)行的5個(gè)task可以重復(fù)使用一個(gè)JVM煞躬,減少啟動(dòng)和關(guān)閉的開銷。但它對(duì)不同MR job中的task無效逸邦。

并行執(zhí)行與本地模式

  • 并行執(zhí)行
    Hive中互相沒有依賴關(guān)系的job間是可以并行執(zhí)行的恩沛,最典型的就是多個(gè)子查詢union all。在集群資源相對(duì)充足的情況下缕减,可以開啟并行執(zhí)行雷客,即將參數(shù)hive.exec.parallel設(shè)為true。另外hive.exec.parallel.thread.number可以設(shè)定并行執(zhí)行的線程數(shù)桥狡,默認(rèn)為8搅裙,一般都?jí)蛴谩?/li>
  • 本地模式
    Hive也可以不將任務(wù)提交到集群進(jìn)行運(yùn)算皱卓,而是直接在一臺(tái)節(jié)點(diǎn)上處理。因?yàn)橄颂峤坏郊旱膐verhead部逮,所以比較適合數(shù)據(jù)量很小娜汁,且邏輯不復(fù)雜的任務(wù)。
    設(shè)置hive.exec.mode.local.auto為true可以開啟本地模式兄朋。但任務(wù)的輸入數(shù)據(jù)總量必須小于hive.exec.mode.local.auto.inputbytes.max(默認(rèn)值128MB)掐禁,且mapper數(shù)必須小于hive.exec.mode.local.auto.tasks.max(默認(rèn)值4),reducer數(shù)必須為0或1颅和,才會(huì)真正用本地模式執(zhí)行穆桂。

嚴(yán)格模式

所謂嚴(yán)格模式,就是強(qiáng)制不允許用戶執(zhí)行3種有風(fēng)險(xiǎn)的HiveQL語句融虽,一旦執(zhí)行會(huì)直接失敗享完。這3種語句是:

  • 查詢分區(qū)表時(shí)不限定分區(qū)列的語句;
  • 兩表join產(chǎn)生了笛卡爾積的語句有额;
  • 用order by來排序但沒有指定limit的語句般又。

要開啟嚴(yán)格模式,需要將參數(shù)hive.mapred.mode設(shè)為strict巍佑。

采用合適的存儲(chǔ)格式

在HiveQL的create table語句中茴迁,可以使用stored as ...指定表的存儲(chǔ)格式。Hive表支持的存儲(chǔ)格式有TextFile萤衰、SequenceFile堕义、RCFile、Avro脆栋、ORC倦卖、Parquet等。
存儲(chǔ)格式一般需要根據(jù)業(yè)務(wù)進(jìn)行選擇椿争,在我們的實(shí)操中怕膛,絕大多數(shù)表都采用TextFile與Parquet兩種存儲(chǔ)格式之一。
TextFile是最簡(jiǎn)單的存儲(chǔ)格式秦踪,它是純文本記錄褐捻,也是Hive的默認(rèn)格式。雖然它的磁盤開銷比較大椅邓,查詢效率也低柠逞,但它更多地是作為跳板來使用。RCFile景馁、ORC板壮、Parquet等格式的表都不能由文件直接導(dǎo)入數(shù)據(jù),必須由TextFile來做中轉(zhuǎn)裁僧。
Parquet和ORC都是Apache旗下的開源列式存儲(chǔ)格式个束。列式存儲(chǔ)比起傳統(tǒng)的行式存儲(chǔ)更適合批量OLAP查詢,并且也支持更好的壓縮和編碼聊疲。我們選擇Parquet的原因主要是它支持Impala查詢引擎茬底,并且我們對(duì)update、delete和事務(wù)性操作需求很低获洲。
這里就不展開講它們的細(xì)節(jié)阱表,可以參考各自的官網(wǎng):
https://parquet.apache.org/
https://orc.apache.org/

結(jié)束

寫了這么多,肯定有遺漏或錯(cuò)誤之處贡珊,歡迎各位大佬批評(píng)指正最爬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市门岔,隨后出現(xiàn)的幾起案子爱致,更是在濱河造成了極大的恐慌,老刑警劉巖寒随,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糠悯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡妻往,警方通過查閱死者的電腦和手機(jī)互艾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讯泣,“玉大人纫普,你說我怎么就攤上這事『们” “怎么了昨稼?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拳锚。 經(jīng)常有香客問我悦昵,道長,這世上最難降的妖魔是什么晌畅? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任但指,我火速辦了婚禮,結(jié)果婚禮上抗楔,老公的妹妹穿的比我還像新娘棋凳。我一直安慰自己,他們只是感情好连躏,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布剩岳。 她就那樣靜靜地躺著,像睡著了一般入热。 火紅的嫁衣襯著肌膚如雪拍棕。 梳的紋絲不亂的頭發(fā)上晓铆,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音绰播,去河邊找鬼骄噪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蠢箩,可吹牛的內(nèi)容都是我干的链蕊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼谬泌,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼滔韵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掌实,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤陪蜻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后贱鼻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體囱皿,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年忱嘹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘱腥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拘悦,死狀恐怖齿兔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情础米,我是刑警寧澤分苇,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站屁桑,受9級(jí)特大地震影響医寿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蘑斧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一靖秩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竖瘾,春花似錦沟突、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至庸论,卻和暖如春职辅,著一層夾襖步出監(jiān)牢的瞬間棒呛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工域携, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留簇秒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓涵亏,卻偏偏與公主長得像宰睡,于是被迫代替她去往敵國和親蒲凶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子气筋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容