Hive企業(yè)級(jí)性能優(yōu)化(建議收藏)
點(diǎn)擊上方 "大數(shù)據(jù)肌肉猿"關(guān)注, 星標(biāo)一起成長(zhǎng)
后臺(tái)回復(fù)【加群】,進(jìn)入高質(zhì)量學(xué)習(xí)交流群
2021年大數(shù)據(jù)肌肉猿公眾號(hào)獎(jiǎng)勵(lì)制度

Hive作為大數(shù)據(jù)平臺(tái)舉足輕重的框架,以其穩(wěn)定性和簡(jiǎn)單易用性也成為當(dāng)前構(gòu)建企業(yè)級(jí)數(shù)據(jù)倉(cāng)庫(kù)時(shí)使用最多的框架之一。
但是如果我們只局限于會(huì)使用Hive,而不考慮性能問(wèn)題,就難搭建出一個(gè)完美的數(shù)倉(cāng),所以Hive性能調(diào)優(yōu)是我們大數(shù)據(jù)從業(yè)者必須掌握的技能。本文將給大家講解Hive性能調(diào)優(yōu)的一些方法及技巧。
Hive性能問(wèn)題排查方式
當(dāng)我們發(fā)現(xiàn)一條SQL語(yǔ)句執(zhí)行時(shí)間過(guò)長(zhǎng)或者不合理時(shí),我們就要考慮對(duì)SQL進(jìn)行優(yōu)化,優(yōu)化首先得進(jìn)行問(wèn)題排查,那么我們可以通過(guò)哪些方式進(jìn)行排查呢。
經(jīng)常使用關(guān)系型數(shù)據(jù)庫(kù)的同學(xué)可能知道關(guān)系型數(shù)據(jù)庫(kù)的優(yōu)化的訣竅-看執(zhí)行計(jì)劃。如Oracle數(shù)據(jù)庫(kù),它有多種類(lèi)型的執(zhí)行計(jì)劃,通過(guò)多種執(zhí)行計(jì)劃的配合使用,可以看到根據(jù)統(tǒng)計(jì)信息推演的執(zhí)行計(jì)劃,即Oracle推斷出來(lái)的未真正運(yùn)行的執(zhí)行計(jì)劃;能夠觀察到從數(shù)據(jù)讀取到最終呈現(xiàn)的主要過(guò)程和中間的量化數(shù)據(jù)??梢哉f(shuō),在Oracle開(kāi)發(fā)領(lǐng)域,掌握合適的環(huán)節(jié),選用不同的執(zhí)行計(jì)劃,SQL調(diào)優(yōu)就不是一件難事。
Hive中也有執(zhí)行計(jì)劃,但是Hive的執(zhí)行計(jì)劃都是預(yù)測(cè)的,這點(diǎn)不像Oracle和SQL Server有真實(shí)的計(jì)劃,可以看到每個(gè)階段的處理數(shù)據(jù)、消耗的資源和處理的時(shí)間等量化數(shù)據(jù)。Hive提供的執(zhí)行計(jì)劃沒(méi)有這些數(shù)據(jù),這意味著雖然Hive的使用者知道整個(gè)SQL的執(zhí)行邏輯,但是各階段耗用的資源狀況和整個(gè)SQL的執(zhí)行瓶頸在哪里是不清楚的。
想要知道HiveSQL所有階段的運(yùn)行信息,可以查看YARN提供的日志。查看日志的鏈接,可以在每個(gè)作業(yè)執(zhí)行后,在控制臺(tái)打印的信息中找到。如下圖所示:

Hive提供的執(zhí)行計(jì)劃目前可以查看的信息有以下幾種:
查看執(zhí)行計(jì)劃的基本信息,即explain; 查看執(zhí)行計(jì)劃的擴(kuò)展信息,即explain extended; 查看SQL數(shù)據(jù)輸入依賴(lài)的信息,即explain dependency; 查看SQL操作相關(guān)權(quán)限的信息,即explain authorization; 查看SQL的向量化描述信息,即explain vectorization。
在查詢(xún)語(yǔ)句的SQL前面加上關(guān)鍵字explain是查看執(zhí)行計(jì)劃的基本方法。用explain打開(kāi)的執(zhí)行計(jì)劃包含以下兩部分:
作業(yè)的依賴(lài)關(guān)系圖,即STAGE DEPENDENCIES; 每個(gè)作業(yè)的詳細(xì)信息,即STAGE PLANS。
Hive中的explain執(zhí)行計(jì)劃詳解可看我之前寫(xiě)的這篇文章:Hive底層原理:explain執(zhí)行計(jì)劃詳解
注:使用explain查看執(zhí)行計(jì)劃是Hive性能調(diào)優(yōu)中非常重要的一種方式,請(qǐng)務(wù)必掌握!
總結(jié):Hive對(duì)SQL語(yǔ)句性能問(wèn)題排查的方式:
使用explain查看執(zhí)行計(jì)劃; 查看YARN提供的日志。
Hive性能調(diào)優(yōu)的方式
為什么都說(shuō)性能優(yōu)化這項(xiàng)工作是比較難的,因?yàn)橐豁?xiàng)技術(shù)的優(yōu)化,必然是一項(xiàng)綜合性的工作,它是多門(mén)技術(shù)的結(jié)合。我們?nèi)绻痪窒抻谝环N技術(shù),那么肯定做不好優(yōu)化的。
下面將從多個(gè)完全不同的角度來(lái)介紹Hive優(yōu)化的多樣性,我們先來(lái)一起感受下。
1. SQL語(yǔ)句優(yōu)化
SQL語(yǔ)句優(yōu)化涉及到的內(nèi)容太多,因篇幅有限,不能一一介紹到,所以就拿幾個(gè)典型舉例,讓大家學(xué)到這種思想,以后遇到類(lèi)似調(diào)優(yōu)問(wèn)題可以往這幾個(gè)方面多思考下。
1. union all
insert into table stu partition(tp)
select s_age,max(s_birth) stat,'max' tp
from stu_ori
group by s_age
union all
insert into table stu partition(tp)
select s_age,min(s_birth) stat,'min' tp
from stu_ori
group by s_age;
我們簡(jiǎn)單分析上面的SQL語(yǔ)句,就是將每個(gè)年齡段的最大和最小的生日獲取出來(lái)放到同一張表中,union all 前后的兩個(gè)語(yǔ)句都是對(duì)同一張表按照s_age進(jìn)行分組,然后分別取最大值和最小值。
上面的SQL對(duì)同一張表的相同字段進(jìn)行兩次分組,這顯然造成了極大浪費(fèi),我們能不能改造下呢,當(dāng)然是可以的,為大家介紹一個(gè)語(yǔ)法: from ... insert into ... ,這個(gè)語(yǔ)法將from前置,作用就是使用一張表,可以進(jìn)行多次插入操作:
--開(kāi)啟動(dòng)態(tài)分區(qū)
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
from stu_ori
insert into table stu partition(tp)
select s_age,max(s_birth) stat,'max' tp
group by s_age
insert into table stu partition(tp)
select s_age,min(s_birth) stat,'min' tp
group by s_age;
上面的SQL就可以對(duì)stu_ori表的s_age字段分組一次而進(jìn)行兩次不同的插入操作。
這個(gè)例子告訴我們一定要多了解SQL語(yǔ)句,如果我們不知道這種語(yǔ)法,一定不會(huì)想到這種方式的。
2. distinct
先看一個(gè)SQL,去重計(jì)數(shù):
select count(1)
from(
select s_age
from stu
group by s_age
) b;
這是簡(jiǎn)單統(tǒng)計(jì)年齡的枚舉值個(gè)數(shù),為什么不用distinct?
select count(distinct s_age)
from stu;
有人說(shuō)因?yàn)樵跀?shù)據(jù)量特別大的情況下使用第一種方式(group by)能夠有效避免Reduce端的數(shù)據(jù)傾斜,但事實(shí)如此嗎?
我們先不管數(shù)據(jù)量特別大這個(gè)問(wèn)題,就當(dāng)前的業(yè)務(wù)和環(huán)境下使用distinct一定會(huì)比上面那種子查詢(xún)的方式效率高。原因有以下幾點(diǎn):
上面進(jìn)行去重的字段是年齡字段,要知道年齡的枚舉值是非常有限的,就算計(jì)算1歲到100歲之間的年齡,s_age的最大枚舉值才100,如果轉(zhuǎn)化成MapReduce來(lái)解釋的話,在Map階段,每個(gè)Map會(huì)對(duì)s_age去重。由于s_age枚舉值有限,因而每個(gè)Map得到的s_age也有限,最終得到reduce的數(shù)據(jù)量也就是map數(shù)量*s_age枚舉值的個(gè)數(shù)。這個(gè)數(shù)量是很小的。
distinct的命令會(huì)在內(nèi)存中構(gòu)建一個(gè)hashtable,查找去重的時(shí)間復(fù)雜度是O(1);group by在不同版本間變動(dòng)比較大,有的版本會(huì)用構(gòu)建hashtable的形式去重,有的版本會(huì)通過(guò)排序的方式, 排序最優(yōu)時(shí)間復(fù)雜度無(wú)法到O(1)。另外,第一種方式(group by)去重會(huì)轉(zhuǎn)化為兩個(gè)任務(wù),會(huì)消耗更多的磁盤(pán)網(wǎng)絡(luò)I/O資源。
最新的Hive 3.0中新增了 count(distinct) 優(yōu)化,通過(guò)配置
hive.optimize.countdistinct,即使真的出現(xiàn)數(shù)據(jù)傾斜也可以自動(dòng)優(yōu)化,自動(dòng)改變SQL執(zhí)行的邏輯。第二種方式(distinct)比第一種方式(group by)代碼簡(jiǎn)潔,表達(dá)的意思簡(jiǎn)單明了,如果沒(méi)有特殊的問(wèn)題,代碼簡(jiǎn)潔就是優(yōu)!
這個(gè)例子告訴我們,有時(shí)候我們不要過(guò)度優(yōu)化,調(diào)優(yōu)講究適時(shí)調(diào)優(yōu),過(guò)早進(jìn)行調(diào)優(yōu)有可能做的是無(wú)用功甚至產(chǎn)生負(fù)效應(yīng),在調(diào)優(yōu)上投入的工作成本和回報(bào)不成正比。調(diào)優(yōu)需要遵循一定的原則。
2. 數(shù)據(jù)格式優(yōu)化
Hive提供了多種數(shù)據(jù)存儲(chǔ)組織格式,不同格式對(duì)程序的運(yùn)行效率也會(huì)有極大的影響。
Hive提供的格式有TEXT、SequenceFile、RCFile、ORC和Parquet等。
SequenceFile是一個(gè)二進(jìn)制key/value對(duì)結(jié)構(gòu)的平面文件,在早期的Hadoop平臺(tái)上被廣泛用于MapReduce輸出/輸出格式,以及作為數(shù)據(jù)存儲(chǔ)格式。
Parquet是一種列式數(shù)據(jù)存儲(chǔ)格式,可以兼容多種計(jì)算引擎,如MapRedcue和Spark等,對(duì)多層嵌套的數(shù)據(jù)結(jié)構(gòu)提供了良好的性能支持,是目前Hive生產(chǎn)環(huán)境中數(shù)據(jù)存儲(chǔ)的主流選擇之一。
ORC優(yōu)化是對(duì)RCFile的一種優(yōu)化,它提供了一種高效的方式來(lái)存儲(chǔ)Hive數(shù)據(jù),同時(shí)也能夠提高Hive的讀取、寫(xiě)入和處理數(shù)據(jù)的性能,能夠兼容多種計(jì)算引擎。事實(shí)上,在實(shí)際的生產(chǎn)環(huán)境中,ORC已經(jīng)成為了Hive在數(shù)據(jù)存儲(chǔ)上的主流選擇之一。
我們執(zhí)行同樣的SQL語(yǔ)句及同樣的數(shù)據(jù),只是數(shù)據(jù)存儲(chǔ)格式不同,得到如下執(zhí)行時(shí)長(zhǎng):
| 數(shù)據(jù)格式 | CPU時(shí)間 | 用戶(hù)等待耗時(shí) |
|---|---|---|
| TextFile | 33分 | 171秒 |
| SequenceFile | 38分 | 162秒 |
| Parquet | 2分22秒 | 50秒 |
| ORC | 1分52秒 | 56秒 |
注:CPU時(shí)間:表示運(yùn)行程序所占用服務(wù)器CPU資源的時(shí)間。
用戶(hù)等待耗時(shí):記錄的是用戶(hù)從提交作業(yè)到返回結(jié)果期間用戶(hù)等待的所有時(shí)間。
查詢(xún)TextFile類(lèi)型的數(shù)據(jù)表耗時(shí)33分鐘, 查詢(xún)ORC類(lèi)型的表耗時(shí)1分52秒,時(shí)間得以極大縮短,可見(jiàn)不同的數(shù)據(jù)存儲(chǔ)格式也能給HiveSQL性能帶來(lái)極大的影響。
3. 小文件過(guò)多優(yōu)化
小文件如果過(guò)多,對(duì) hive 來(lái)說(shuō),在進(jìn)行查詢(xún)時(shí),每個(gè)小文件都會(huì)當(dāng)成一個(gè)塊,啟動(dòng)一個(gè)Map任務(wù)來(lái)完成,而一個(gè)Map任務(wù)啟動(dòng)和初始化的時(shí)間遠(yuǎn)遠(yuǎn)大于邏輯處理的時(shí)間,就會(huì)造成很大的資源浪費(fèi)。而且,同時(shí)可執(zhí)行的Map數(shù)量是受限的。
所以我們有必要對(duì)小文件過(guò)多進(jìn)行優(yōu)化,關(guān)于小文件過(guò)多的解決的辦法,我之前專(zhuān)門(mén)寫(xiě)了一篇文章講解,具體可查看:解決hive小文件過(guò)多問(wèn)題。
4. 并行執(zhí)行優(yōu)化
Hive會(huì)將一個(gè)查詢(xún)轉(zhuǎn)化成一個(gè)或者多個(gè)階段。這樣的階段可以是MapReduce階段、抽樣階段、合并階段、limit階段。或者Hive執(zhí)行過(guò)程中可能需要的其他階段。默認(rèn)情況下,Hive一次只會(huì)執(zhí)行一個(gè)階段。不過(guò),某個(gè)特定的job可能包含眾多的階段,而這些階段可能并非完全互相依賴(lài)的,也就是說(shuō)有些階段是可以并行執(zhí)行的,這樣可能使得整個(gè)job的執(zhí)行時(shí)間縮短。如果有更多的階段可以并行執(zhí)行,那么job可能就越快完成。
通過(guò)設(shè)置參數(shù)hive.exec.parallel值為true,就可以開(kāi)啟并發(fā)執(zhí)行。在共享集群中,需要注意下,如果job中并行階段增多,那么集群利用率就會(huì)增加。
set hive.exec.parallel=true; //打開(kāi)任務(wù)并行執(zhí)行
set hive.exec.parallel.thread.number=16; //同一個(gè)sql允許最大并行度,默認(rèn)為8。
當(dāng)然得是在系統(tǒng)資源比較空閑的時(shí)候才有優(yōu)勢(shì),否則沒(méi)資源,并行也起不來(lái)。
5. JVM優(yōu)化
JVM重用是Hadoop調(diào)優(yōu)參數(shù)的內(nèi)容,其對(duì)Hive的性能具有非常大的影響,特別是對(duì)于很難避免小文件的場(chǎng)景或task特別多的場(chǎng)景,這類(lèi)場(chǎng)景大多數(shù)執(zhí)行時(shí)間都很短。
Hadoop的默認(rèn)配置通常是使用派生JVM來(lái)執(zhí)行map和Reduce任務(wù)的。這時(shí)JVM的啟動(dòng)過(guò)程可能會(huì)造成相當(dāng)大的開(kāi)銷(xiāo),尤其是執(zhí)行的job包含有成百上千task任務(wù)的情況。JVM重用可以使得JVM實(shí)例在同一個(gè)job中重新使用N次。N的值可以在Hadoop的mapred-site.xml文件中進(jìn)行配置。通常在10-20之間,具體多少需要根據(jù)具體業(yè)務(wù)場(chǎng)景測(cè)試得出。
<property>
<name>mapreduce.job.jvm.numtasks</name>
<value>10</value>
<description>How many tasks to run per jvm. If set to -1, there is
no limit.
</description>
</property>
我們也可以在hive中設(shè)置
set mapred.job.reuse.jvm.num.tasks=10; //這個(gè)設(shè)置來(lái)設(shè)置我們的jvm重用
這個(gè)功能的缺點(diǎn)是,開(kāi)啟JVM重用將一直占用使用到的task插槽,以便進(jìn)行重用,直到任務(wù)完成后才能釋放。如果某個(gè)“不平衡的”job中有某幾個(gè)reduce task執(zhí)行的時(shí)間要比其他Reduce task消耗的時(shí)間多的多的話,那么保留的插槽就會(huì)一直空閑著卻無(wú)法被其他的job使用,直到所有的task都結(jié)束了才會(huì)釋放。
6. 推測(cè)執(zhí)行優(yōu)化
在分布式集群環(huán)境下,因?yàn)槌绦騜ug(包括Hadoop本身的bug),負(fù)載不均衡或者資源分布不均等原因,會(huì)造成同一個(gè)作業(yè)的多個(gè)任務(wù)之間運(yùn)行速度不一致,有些任務(wù)的運(yùn)行速度可能明顯慢于其他任務(wù)(比如一個(gè)作業(yè)的某個(gè)任務(wù)進(jìn)度只有50%,而其他所有任務(wù)已經(jīng)運(yùn)行完畢),則這些任務(wù)會(huì)拖慢作業(yè)的整體執(zhí)行進(jìn)度。為了避免這種情況發(fā)生,Hadoop采用了推測(cè)執(zhí)行(Speculative Execution)機(jī)制,它根據(jù)一定的法則推測(cè)出“拖后腿”的任務(wù),并為這樣的任務(wù)啟動(dòng)一個(gè)備份任務(wù),讓該任務(wù)與原始任務(wù)同時(shí)處理同一份數(shù)據(jù),并最終選用最先成功運(yùn)行完成任務(wù)的計(jì)算結(jié)果作為最終結(jié)果。
設(shè)置開(kāi)啟推測(cè)執(zhí)行參數(shù):Hadoop的mapred-site.xml文件中進(jìn)行配置:
<property>
<name>mapreduce.map.speculative</name>
<value>true</value>
<description>If true, then multiple instances of some map tasks
may be executed in parallel.</description>
</property>
<property>
<name>mapreduce.reduce.speculative</name>
<value>true</value>
<description>If true, then multiple instances of some reduce tasks
may be executed in parallel.</description>
</property>
hive本身也提供了配置項(xiàng)來(lái)控制reduce-side的推測(cè)執(zhí)行:
set hive.mapred.reduce.tasks.speculative.execution=true
關(guān)于調(diào)優(yōu)這些推測(cè)執(zhí)行變量,還很難給一個(gè)具體的建議。如果用戶(hù)因?yàn)檩斎霐?shù)據(jù)量很大而需要執(zhí)行長(zhǎng)時(shí)間的map或者reduce task的話,那么啟動(dòng)推測(cè)執(zhí)行造成的浪費(fèi)是非常巨大的。
最后
代碼優(yōu)化原則:
理透需求原則,這是優(yōu)化的根本; 把握數(shù)據(jù)全鏈路原則,這是優(yōu)化的脈絡(luò); 堅(jiān)持代碼的簡(jiǎn)潔原則,這讓優(yōu)化更加簡(jiǎn)單; 沒(méi)有瓶頸時(shí)談?wù)搩?yōu)化,這是自尋煩惱。
--end--
掃描下方二維碼 添加好友,備注【交流】 可私聊交流,也可進(jìn)資源豐富學(xué)習(xí)群
更文不易,點(diǎn)個(gè)“在看”支持一下??
