Hive常用效能優化方法實踐全面總結
Apache Hive作為處理大資料量的大資料領域資料建設核心工具,資料量往往不是影響Hive執行效率的核心因素,資料傾斜、job數分配的不合理、磁碟或網路I/O過高、MapReduce配置的不合理等等才是影響Hive效能的關鍵。
Hive在執行任務時,通常會將Hive SQL轉化為MapReduce job進行處理。因此對Hive的調優,除了對Hive語句本身的優化,也要考慮Hive配置項以及MapReduce相關的優化。從更底層思考如何優化效能,而不是僅僅侷限於程式碼/SQL的層面。
列裁剪和分割槽裁剪
Hive在讀資料的時候,只讀取查詢中所需要用到的列,而忽略其它列。例如,若有以下查詢:
SELECT age, name FROM people WHERE age > 30;
在實施此項查詢中,people表有3列(age,name,address),Hive只讀取查詢邏輯中真正需要的兩列age、name,而忽略列address;這樣做節省了讀取開銷,中間表儲存開銷和資料整合開銷。
同理,對於Hive分割槽表的查詢,我們在寫SQL時,通過指定實際需要的分割槽,可以減少不必要的分割槽資料掃描【當Hive表中列很多或者資料量很大時,如果直接使用select * 或者不指定分割槽,效率會很低下(全列掃描和全表掃描)】。
Hive中與列裁剪和分割槽裁剪優化相關的配置引數分別為:hive.optimize.cp和hive.optimize.pruner,預設都是true。
謂詞下推
在關係型資料庫如MySQL中,也有謂詞下推(Predicate Pushdown,PPD)的概念。它就是將SQL語句中的where謂詞邏輯都儘可能提前執行,減少下游處理的資料量。
如下Hive SQL語句:
select a.*, b.* from a join b on (a.id = b.id)where a.id > 15 and b.num > 16;
如果沒有謂詞下推,上述SQL需要在完成join處理之後才會執行where條件過濾。在這種情況下,參與join的資料可能會非常多,從而影響執行效率。
使用謂詞下推,那麼where條件會在join之前被處理,參與join的資料量減少,提升效率。
在Hive中,可以通過將引數hive.optimize.ppd設定為true,啟用謂詞下推。與它對應的邏輯優化器是PredicatePushDown。該優化器就是將OperatorTree中的FilterOperator向上提,見下圖:
Hive join優化
關於Hive join,參考文章:《Hive join優化》。
hive.fetch.task.conversion
雖然Hive底層可以將Hive SQL轉化為MapReduce執行,但有些情況不使用MapReduce處理效率跟高。比如對於如下SQL:
SELECT name FROM people;
在這種情況下,Hive可以簡單地讀取people對應的儲存目錄下的檔案,然後返回資料。
在hive-default.xml.template檔案中hive.fetch.task.conversion預設是more,老版本hive預設是minimal,該屬性修改為more以後,在全域性查詢、欄位查詢、limit查詢等都不走mapreduce。
<property> <name>hive.fetch.task.conversion</name> <value>more</value> <description> Expects one of [none, minimal, more]. Some select queries can be converted to single FETCH task minimizing latency. Currently the query should be single sourced not having any subquery and should not have any aggregations or distincts (which incurs RS), lateral views and joins. 0. none : disable hive.fetch.task.conversion 1. minimal : SELECT STAR, FILTER on partition columns, LIMIT only 2. more : SELECT, FILTER, LIMIT only (support TABLESAMPLE and virtual columns) </description> </property>
將hive.fetch.task.conversion設定成none,在Hive shell中執行如下語句,都會執行MapReduce程式。
hive> set hive.fetch.task.conversion=none; hive> select name from people; hive> select * from people;
把hive.fetch.task.conversion設定成more,然後執行如下語句,如下查詢方式都不會執行MapReduce程式。
hive> set hive.fetch.task.conversion=more; hive> select name from people; hive> select * from people;
通過引數說明發現當把hive.fetch.task.conversion設定成none時,所有的程式都走mapreduce程式會耗費一定的時間。但就算設定成more,也只有部分sql語句會不走MapReduce程式,那有沒有什麼辦法可以優化這個問題呢?這就不得不提本地模式了。
group by
1)map端預聚合
通過在map端進行一次預聚合(起一個combiner),可以有效減少shuffle的資料量,然後再在reduce端得到最終結果。
預聚合的配置引數為hive.map.aggr,預設值true。
此外,通過hive.groupby.mapaggr.checkinterval引數可以設定map端預聚合的條數閾值,超過該值就會分拆job,預設值100000。
2)資料傾斜時進行負載均衡處理
當group by時,如果某些key對應的資料量過大,會導致資料傾斜。
通過將引數hive.groupby.skewindata(預設false)設定為true,那麼在進行group by時,會啟動兩個MR job。第一個job會將map端資料隨機輸入reducer,每個reducer做部分聚合操作,相同的group by key會分佈在不同的reducer中。第二個job再將前面預處理過的資料按key聚合並輸出結果,這樣就起到了均衡的效果。
但是,相對於正常的任務執行,該引數配置為true時會多啟動一個MR job,會增加開銷,單純依賴它解決資料傾斜並不能從根本上解決問題。因此,建議分析資料、Hive SQL語句等,瞭解產生資料傾斜的根本原因進行解決。
count(distinct)
count(distinct)採用非常少的reducer進行資料處理。資料量小時對執行效率影響不明顯,但是當資料量大時,效率會很低,尤其是資料傾斜的時候。
可以通過group by代替count(distinct)使用。示例如下:
原SQL:SELECT count(DISTINCT id) FROM people; group by替換後:SELECT count(id) FROM (SELECT id FROM people GROUP BY id) tmp;
注意:上述group by替換後,會啟動兩個MR job(只是distinct只會啟動一個),所以要確保啟動job的開銷遠小於計算耗時,才考慮這種方法。否則當資料集很小或者key的傾斜不明顯時,group by還可能會比count(distinct)還慢。
此外,如何用group by方式同時統計多個列?下面提供一種SQL方案:
select tmp.a, sum(tmp.b), count(tmp.c), count(tmp.d) from ( select a, b, null c, null d from some_table union all select a, 0 b, c, null d from some_table group by a,c union all select a, 0 b, null c, d from some_table group by a,d ) tmp;
笛卡爾積
除非業務需要,在生產中要極力避免笛卡爾積,比如在join語句中不指定on連線條件,或者無效的on連線條件,Hive只能使用1個reducer來完成笛卡爾積。
本地模式
對於處理小資料量的任務,我們不需要通過叢集模式進行處理(因為為該任務實際觸發的job執行等開銷可能比實際任務的執行時間還要長),Hive可以通過本地模式在單臺機器上處理所有的任務。
可以通過設定hive.exec.mode.local.auto的值為true,來讓Hive在適當的時候自動啟動這個優化。
set hive.exec.mode.local.auto=true;
設定本地MR的最大輸入資料量,當輸入資料量小於這個值時採用本地MR的方式,預設為134217728,即128M
set hive.exec.mode.local.auto.inputbytes.max=51234560;
設定本地MR的最大輸入檔案個數,當輸入檔案個數小於這個值時採用本地MR的方式,預設為4
set hive.exec.mode.local.auto.input.files.max=10;
left semi join替代in/exsits
left semi join是in、exists的高效實現。比如,對於如下SQL
select t1.id, t1.name from t1 where t1.id in (select t2.id from t2);
改為left semi join執行:
select t1.id, t1.name from t1 left semi join t2 on t1.id = t2.id;
MapReduce相關的優化
1. mapper和reducer個數
關於MapReduce中mapper和reducer個數的決定機制,建議閱讀文章:《詳解MapReduce》
2. 合併小檔案
1)輸入階段合併
設定引數hive.input.format為org.apache.hadoop.hive.ql.io.CombineHiveInputFormat。(預設值是org.apache.hadoop.hive.ql.io.HiveInputFormat)。
此外還需配置兩個引數:mapred.min.split.size.per.node(單節點上的最小split大小)和mapred.min.split.size.per.rack(單機架上的最小split大小)。
如果有split大小小於這兩個值,則會進行合併。
2)輸出階段合併
將hive.merge.mapfiles和hive.merge.mapredfiles都設為true,前者表示將map-only任務的輸出合併,後者表示將map-reduce任務的輸出合併。
此外,hive.merge.size.per.task可以指定每個task輸出後合併檔案大小的期望值,hive.merge.size.smallfiles.avgsize可以指定所有輸出檔案大小的均值閾值。如果平均大小不足的話,就會另外啟動一個任務來進行合併。
3. 啟用壓縮
壓縮job的中間結果資料和輸出資料,可以用少量CPU時間節省很多空間,壓縮方式一般選擇Snappy。(關於Hadoop支援的壓縮格式,參考文章:《 Hadoop支援的壓縮格式對比和應用場景以及Hadoop native庫 》)
要啟用中間壓縮,需要設定hive.exec.compress.intermediate為true,同時指定壓縮方式hive.intermediate.compression.codec為org.apache.hadoop.io.compress.SnappyCodec。
另外,引數hive.intermediate.compression.type可以選擇對塊(BLOCK)還是記錄(RECORD)壓縮,BLOCK的壓縮率比較高。
輸出壓縮的配置基本相同,開啟hive.exec.compress.output即可。
4. JVM重用
在MR job中,預設是每執行一個task就啟動一個JVM。可以通過配置引數mapred.job.reuse.jvm.num.tasks來進行JVM重用。
例如將這個引數設成5,那麼就代表同一個MR job中順序執行的5個task可以重複使用一個JVM,減少啟動和關閉的開銷。但它對不同MR job中的task無效。
採用合適的儲存格式
在HiveQL的create table語句中,可以使用stored as ...指定表的儲存格式。Hive目前支援的儲存格式有TextFile、SequenceFile、RCFile、avro、orc、parquet等。
當然,我們也可以採用alter table … [PARTITION partition_spec] set fileformat,修改具體表的檔案格式
parquet和orc是企業中常用的兩種資料儲存格式,具體可以參考官網:
https://parquet.apache.org/和https://orc.apache.org/。
推測執行
在分散式叢集環境下,由於負載不均衡或者資源分佈不均等原因,會造成同一個作業的多個job之間執行速度不一致,有些job的執行速度可能明顯慢於其他任務,則這些job會拖慢整個作業的執行進度。為了避免這種情況發生,Hadoop採用了推測執行(Speculative Execution)機制。
"推測執行"機制,根據一定的規則推測出"拖後腿"的任務,併為這樣的任務啟動一個備份任務,讓該任務與原始任務同時處理同一份資料,並最終選用最先成功執行完成任務的計算結果作為最終結果。
Hive同樣可以開啟推測執行。設定開啟推測執行引數(在配置檔案mapred-site.xml中進行配置)
<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本身也提供了配置項來控制reduce-side的推測執行:
<property> <name>hive.mapred.reduce.tasks.speculative.execution</name> <value>true</value> <description>Whether speculative execution for reducers should be turned on. </description> </property>
關於調優這些推測執行變數,目前還很難給出一個具體建議。如果使用者對於執行時的偏差非常敏感的話,那麼可以將這些功能關閉掉。如果使用者因為輸入資料量很大而需要執行長時間的map或者reduce task的話,那麼啟動推測執行造成的浪費是非常巨大。
推薦文章:
一次Java記憶體洩漏的排查
經典的SparkSQL/Hive-SQL/MySQL面試-練習題
基於Hive進行數倉建設的資源元資料資訊統計
Hive Query生命週期 —— 鉤子(Hook)函式篇
Hive實現自增序列及元資料問題
資料倉庫架構和建設方法論
關注微信公眾號:大資料學習與分享,獲取更對技術