1. 程式人生 > 其它 >Hive引數與效能企業級調優

Hive引數與效能企業級調優

Hive作為大資料平臺舉足輕重的框架,以其穩定性和簡單易用性也成為當前構建企業級資料倉庫時使用最多的框架之一。

但是如果我們只侷限於會使用Hive,而不考慮效能問題,就難搭建出一個完美的數倉,所以Hive效能調優是我們大資料從業者必須掌握的技能。本文將給大家講解Hive效能調優的一些方法及技巧。

Hive效能調優的方式

為什麼都說效能優化這項工作是比較難的,因為一項技術的優化,必然是一項綜合性的工作,它是多門技術的結合。我們如果只侷限於一種技術,那麼肯定做不好優化的。

下面將從多個完全不同的角度來介紹Hive優化的多樣性,我們先來一起感受下。

1. SQL語句優化

SQL語句優化涉及到的內容太多,因篇幅有限,不能一一介紹到,所以就拿幾個典型舉例,讓大家學到這種思想,以後遇到類似調優問題可以往這幾個方面多思考下。

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;

我們簡單分析上面的SQl語句,就是將每個年齡的最大和最小的生日獲取出來放到同一張表中,union all 前後的兩個語句都是對同一張表按照s_age進行分組,然後分別取最大值和最小值。對同一張表相同的欄位進行兩次分組,這造成了極大浪費,我們能不能改造下呢,當然是可以的,為大家介紹一個語法:
from ... insert into ...

,這個語法將from前置,作用就是使用一張表,可以進行多次插入操作:

--開啟動態分割槽 
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就可以對stu_ori表的s_age欄位分組一次而進行兩次不同的插入操作。

這個例子告訴我們一定要多瞭解SQL語句,如果我們不知道這種語法,一定不會想到這種方式的

2. distinct

先看一個SQL,去重計數:

select count(1) 
from( 
  select s_age 
  from stu 
  group by s_age 
) b;

這是簡單統計年齡的列舉值個數,為什麼不用distinct?

select count(distinct s_age) 
from stu;

有人說因為在資料量特別大的情況下使用第一種方式能夠有效避免Reduce端的資料傾斜,但是事實如此嗎?

我們先不管資料量特別大這個問題,就當前的業務和環境下使用distinct一定會比上面那種子查詢的方式效率高。原因有以下幾點:

  1. 上面進行去重的欄位是年齡欄位,要知道年齡的列舉值是非常有限的,就算計算1歲到100歲之間的年齡,s_age的最大列舉值才是100,如果轉化成MapReduce來解釋的話,在Map階段,每個Map會對s_age去重。由於s_age列舉值有限,因而每個Map得到的s_age也有限,最終得到reduce的資料量也就是map數量*s_age列舉值的個數。

  2. distinct的命令會在記憶體中構建一個hashtable,查詢去重的時間複雜度是O(1);group by在不同版本間變動比較大,有的版本會用構建hashtable的形式去重,有的版本會通過排序的方式, 排序最優時間複雜度無法到O(1)。另外,第一種方式(group by)去重會轉化為兩個任務,會消耗更多的磁碟網路I/O資源。

  3. 最新的Hive 3.0中新增了 count(distinct ) 優化,通過配置 hive.optimize.countdistinct,即使真的出現數據傾斜也可以自動優化,自動改變SQL執行的邏輯。

  4. 第二種方式(distinct)比第一種方式(group by)程式碼簡潔,表達的意思簡單明瞭,如果沒有特殊的問題,程式碼簡潔就是優!

這個例子告訴我們,有時候我們不要過度優化,調優講究適時調優,過早進行調優有可能做的是無用功甚至產生負效應,在調優上投入的工作成本和回報不成正比。調優需要遵循一定的原則

2. 資料格式優化

Hive提供了多種資料儲存組織格式,不同格式對程式的執行效率也會有極大的影響。

Hive提供的格式有TEXT、SequenceFile、RCFile、ORC和Parquet等。

SequenceFile是一個二進位制key/value對結構的平面檔案,在早期的Hadoop平臺上被廣泛用於MapReduce輸出/輸出格式,以及作為資料儲存格式。

Parquet是一種列式資料儲存格式,可以相容多種計算引擎,如MapRedcue和Spark等,對多層巢狀的資料結構提供了良好的效能支援,是目前Hive生產環境中資料儲存的主流選擇之一。

ORC優化是對RCFile的一種優化,它提供了一種高效的方式來儲存Hive資料,同時也能夠提高Hive的讀取、寫入和處理資料的效能,能夠相容多種計算引擎。事實上,在實際的生產環境中,ORC已經成為了Hive在資料儲存上的主流選擇之一。

我們使用同樣資料及SQL語句,只是資料儲存格式不同,得到如下執行時長:

資料格式 CPU時間 使用者等待耗時
TextFile 33分 171秒
SequenceFile 38分 162秒
Parquet 2分22秒 50秒
ORC 1分52秒 56秒

注:CPU時間:表示執行程式所佔用伺服器CPU資源的時間。
使用者等待耗時:記錄的是使用者從提交作業到返回結果期間使用者等待的所有時間。

查詢TextFile型別的資料表CPU耗時33分鐘, 查詢ORC型別的表耗時1分52秒,時間得以極大縮短,可見不同的資料儲存格式也能給HiveSQL效能帶來極大的影響。

3. 小檔案過多優化

小檔案如果過多,對 hive 來說,在進行查詢時,每個小檔案都會當成一個塊,啟動一個Map任務來完成,而一個Map任務啟動和初始化的時間遠遠大於邏輯處理的時間,就會造成很大的資源浪費。而且,同時可執行的Map數量是受限的。

所以我們有必要對小檔案過多進行優化,關於小檔案過多的解決的辦法,我之前專門寫了一篇文章講解,具體可檢視:

解決hive小檔案過多問題

4. 並行執行優化

Hive會將一個查詢轉化成一個或者多個階段。這樣的階段可以是MapReduce階段、抽樣階段、合併階段、limit階段。或者Hive執行過程中可能需要的其他階段。預設情況下,Hive一次只會執行一個階段。不過,某個特定的job可能包含眾多的階段,而這些階段可能並非完全互相依賴的,也就是說有些階段是可以並行執行的,這樣可能使得整個job的執行時間縮短。如果有更多的階段可以並行執行,那麼job可能就越快完成。

通過設定引數hive.exec.parallel值為true,就可以開啟併發執行。在共享叢集中,需要注意下,如果job中並行階段增多,那麼叢集利用率就會增加。

set hive.exec.parallel=true; //開啟任務並行執行
set hive.exec.parallel.thread.number=16; //同一個sql允許最大並行度,預設為8。

當然得是在系統資源比較空閒的時候才有優勢,否則沒資源,並行也起不來。

5. 資料傾斜優化

資料傾斜的原理都知道,就是某一個或幾個key佔據了整個資料的90%,這樣整個任務的效率都會被這個key的處理拖慢,同時也可能會因為相同的key會聚合到一起造成記憶體溢位。

Hive的資料傾斜一般的處理方案

常見的做法,通過引數調優:


set hive.map.aggr=true;  
set hive.groupby.skewindata = ture;

當選項設定為true時,生成的查詢計劃有兩個MapReduce任務。

在第一個MapReduce中,map的輸出結果集合會隨機分佈到reduce中,每個reduce做部分聚合操作,並輸出結果。

這樣處理的結果是,相同的Group By Key有可能分發到不同的reduce中,從而達到負載均衡的目的;

第二個MapReduce任務再根據預處理的資料結果按照Group By Key分佈到reduce中(這個過程可以保證相同的Group By Key分佈到同一個reduce中),最後完成最終的聚合操作。

但是這個處理方案對於我們來說是個黑盒,無法把控。

那麼在日常需求的情況下如何處理這種資料傾斜的情況呢:

  1. sample取樣,獲取哪些集中的key;

  2. 將集中的key按照一定規則新增隨機數;

  3. 進行join,由於打散了,所以資料傾斜避免了;

  4. 在處理結果中對之前的新增的隨機數進行切分,變成原始的資料。

例:如發現有90%的key都是null,資料量一旦過大必然出現數據傾斜,可採用如下方式:

SELECT *
FROM a
 LEFT JOIN b ON CASE 
   WHEN a.user_id IS NULL THEN concat('hive_', rand())
   ELSE a.user_id
  END = b.user_id;

注意:給null值隨機賦的值不要與表中已有的值重複,不然會導致結果錯誤。

6. Limit 限制調整優化

一般情況下,Limit語句還是需要執行整個查詢語句,然後再返回部分結果。

有一個配置屬性可以開啟,避免這種情況:對資料來源進行抽樣

hive.limit.optimize.enable=true -- 開啟對資料來源進行取樣的功能

hive.limit.row.max.size -- 設定最小的取樣容量

hive.limit.optimize.limit.file -- 設定最大的取樣樣本數

缺點:有可能部分資料永遠不會被處理到

7. JOIN優化

1. 使用相同的連線鍵

當對3個或者更多個表進行join連線時,如果每個on子句都使用相同的連線鍵的話,那麼只會產生一個MapReduce job。

2. 儘量儘早地過濾資料

減少每個階段的資料量,對於分割槽表要加分割槽,同時只選擇需要使用到的欄位。

3. 儘量原子化操作

儘量避免一個SQL包含複雜邏輯,可以使用中間表來完成複雜的邏輯。

8. 謂詞下推優化

Hive中的 Predicate Pushdown 簡稱謂詞下推,簡而言之,就是在不影響結果的情況下,儘量將過濾條件下推到join之前進行。謂詞下推後,過濾條件在map端執行,減少了map端的輸出,降低了資料在叢集上傳輸的量,節約了叢集的資源,也提升了任務的效能。

我們看下面這個語句:

select s1.key, s2.key 
from s1 left join s2 
on s1.key > '2';

上面是一個Left Join語句,s1是左表,稱為保留行表,s2是右表。

:on條件的s1.key > '2' 是在join之前執行還是之後?也就是會不會進行謂詞下推?

:不會進行謂詞下推,因為s1是保留行表,過濾條件會在join之後執行。

而下面這個語句:

select s1.key, s2.key 
from s1 left join s2 
on s2.key > '2';

s2表不是保留行,所以s2.key>2條件可以下推到s2表中,也就是join之前執行。

再看下面這個語句:

select s1.key, s2.key 
from s1 left join s2 
where s1.key > '2';

右表s2為NULL補充表。

s1不是NULL補充表,所以s1.key>2可以進行謂詞下推。

而下面語句:

select s1.key, s2.key 
from s1 left join s2 
where s2.key > '2';

由於s2為NULL補充表,所以s2.key>2過濾條件不能下推。

那麼謂詞下推的規則是什麼,到底什麼時候會進行下推,什麼時候不會下推,總結了下面的一張表,建議收藏儲存:

案例

select a.*  
from a  
left join b on  a.uid = b.uid  
where a.ds='2020-08-10'  
and b.ds='2020-08-10'

上面這個SQL主要犯了兩個錯誤

  1. 右表(上方b表)的where條件寫在join後面,會導致先全表關聯在過濾分割槽。

注:雖然a表的where條件也寫在join後面,但是a表會進行謂詞下推,也就是先執行where條件,再執行join,但是b表不會進行謂詞下推!

  1. on的條件沒有過濾null值的情況,如果兩個資料表存在大批量null值的情況,會造成資料傾斜。