Hive 優化策略

 

正文

一、Hadoop 框架計算特性

1、數據量大不是問題,數據傾斜是個問題

2、jobs 數比較多的作業運行效率相對比較低,比如即使有幾百行的表,如果多次關聯多次 彙總,產生十幾個 jobs,耗時很長。原因是 map reduce 作業初始化的時間是比較長的

3、sum,count,max,min 等 UDAF,不怕數據傾斜問題,hadoop 在 map 端的彙總合併優化,使 數據傾斜不成問題

4、count(distinct userid),在數據量大的情況下,效率較低,如果是多 count(distinct userid,month)效率更低,因爲 count(distinct)是按 group by 字段分組,按 distinct 字段排序, 一般這種分佈方式是很

傾斜的,比如 PV 數據,淘寶一天 30 億的 pv,如果按性別分組,分 配 2 個 reduce,每個 reduce 期望處理 15 億數據,但現實必定是男少女多

二、優化常用手段

1、好的模型設計事半功倍

2、解決數據傾斜問題

3、減少 job 數

4、設置合理的 MapReduce 的 task 數,能有效提升性能。(比如,10w+級別的計算,用 160個 reduce,那是相當的浪費,1 個足夠)

5、瞭解數據分佈,自己動手解決數據傾斜問題是個不錯的選擇。這是通用的算法優化,但 算法優化有時不能適應特定業務背景,開發人員瞭解業務,瞭解數據,可以通過業務邏輯精 確有效的解決數據傾斜問題

6、數據量較大的情況下,慎用 count(distinct),group by 容易產生傾斜問題

7、對小文件進行合併,是行之有效的提高調度效率的方法,假如所有的作業設置合理的文 件數,對雲梯的整體調度效率也會產生積極的正向影響

8、優化時把握整體,單個作業最優不如整體最優

三、排序選擇

cluster by:對同一字段分桶並排序,不能和 sort by 連用

distribute by + sort by:分桶,保證同一字段值只存在一個結果文件當中,結合 sort by 保證 每個 reduceTask 結果有序

sort by:單機排序,單個 reduce 結果有序

order by:全局排序,缺陷是隻能使用一個 reduce

一定要區分這四種排序的使用方式和適用場景

四、怎樣做笛卡爾積

當 Hive 設定爲嚴格模式(hive.mapred.mode=strict)時,不允許在 HQL 語句中出現笛卡爾積, 這實際說明了 Hive 對笛卡爾積支持較弱。因爲找不到 Join key,Hive 只能使用 1 個 reducer 來完成笛卡爾積。

當然也可以使用 limit 的辦法來減少某個表參與 join 的數據量,但對於需要笛卡爾積語義的 需求來說,經常是一個大表和一個小表的 Join 操作,結果仍然很大(以至於無法用單機處 理),這時 MapJoin纔是最好的解決辦法。MapJoin,顧名思義,會在 Map 端完成 Join 操作。 這需要將 Join 操作的一個或多個表完全讀入內存。

PS:MapJoin 在子查詢中可能出現未知 BUG。在大表和小表做笛卡爾積時,規避笛卡爾積的 方法是,給 Join 添加一個 Join key,原理很簡單:將小表擴充一列 join key,並將小表的條 目複製數倍,join key 各不相同;將大表擴充一列 join key 爲隨機數。

精髓就在於複製幾倍,最後就有幾個 reduce 來做,而且大表的數據是前面小表擴張 key 值 範圍裏面隨機出來的,所以複製了幾倍 n,就相當於這個隨機範圍就有多大 n,那麼相應的, 大表的數據就被隨機的分爲了 n 份。並且最後處理所用的 reduce 數量也是 n,而且也不會 出現數據傾斜。

五、怎樣寫 in/exists 語句

雖然經過測驗,hive1.2.1 也支持 in/exists 操作,但還是推薦使用 hive 的一個高效替代方案:left semi join

比如說:

select a.id, a.name from a where a.id in (select b.id from b);
select a.id, a.name from a where exists (select id from b where a.id = b.id);

應該轉換成:

select a.id, a.name from a left semi join b on a.id = b.id;

六、設置合理的 maptask 數量

Map 數過大

  Map 階段輸出文件太小,產生大量小文件

  初始化和創建 Map 的開銷很大

Map 數太小

  文件處理或查詢併發度小,Job 執行時間過長

  大量作業時,容易堵塞集羣 

在 MapReduce 的編程案例中,我們得知,一個MR Job的 MapTask 數量是由輸入分片 InputSplit 決定的。而輸入分片是由 FileInputFormat.getSplit()決定的。一個輸入分片對應一個 MapTask, 而輸入分片是由三個參數決定的:

輸入分片大小的計算是這麼計算出來的:

long splitSize = Math.max(minSize, Math.min(maxSize, blockSize))

默認情況下,輸入分片大小和 HDFS 集羣默認數據塊大小一致,也就是默認一個數據塊,啓 用一個 MapTask 進行處理,這樣做的好處是避免了服務器節點之間的數據傳輸,提高 job 處 理效率

兩種經典的控制 MapTask 的個數方案:減少 MapTask 數或者增加 MapTask 數

1、 減少 MapTask 數是通過合併小文件來實現,這一點主要是針對數據源

2、 增加 MapTask 數可以通過控制上一個 job 的 reduceTask 個數 

因爲 Hive 語句最終要轉換爲一系列的 MapReduce Job 的,而每一個 MapReduce Job 是由一 系列的 MapTask 和 ReduceTask 組成的,默認情況下, MapReduce 中一個 MapTask 或者一個 ReduceTask 就會啓動一個 JVM 進程,一個 Task 執行完畢後, JVM 進程就退出。這樣如果任 務花費時間很短,又要多次啓動 JVM 的情況下,JVM 的啓動時間會變成一個比較大的消耗, 這個時候,就可以通過重用 JVM 來解決:

 set mapred.job.reuse.jvm.num.tasks=5 

七、小文件合併

文件數目過多,會給 HDFS 帶來壓力,並且會影響處理效率,可以通過合併 Map 和 Reduce 的 結果文件來消除這樣的影響:

set hive.merge.mapfiles = true ##在 map only 的任務結束時合併小文件

set hive.merge.mapredfiles = false ## true 時在 MapReduce 的任務結束時合併小文件

set hive.merge.size.per.task = 256*1000*1000 ##合併文件的大小

set mapred.max.split.size=256000000; ##每個 Map 最大分割大小

set mapred.min.split.size.per.node=1; ##一個節點上 split 的最少值

set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; ##執行 Map 前進行小文件合併

八、設置合理的 reduceTask 的數量

Hadoop MapReduce 程序中,reducer 個數的設定極大影響執行效率,這使得 Hive 怎樣決定 reducer 個數成爲一個關鍵問題。遺憾的是 Hive 的估計機制很弱,不指定 reducer 個數的情 況下,Hive 會猜測確定一個 reducer 個數,基於以下兩個設定:

1、hive.exec.reducers.bytes.per.reducer(默認爲 256000000)

2、hive.exec.reducers.max(默認爲 1009)

3、mapreduce.job.reduces=-1(設置一個常量 reducetask 數量)

計算 reducer 數的公式很簡單: N=min(參數 2,總輸入數據量/參數 1) 通常情況下,有必要手動指定 reducer 個數。考慮到 map 階段的輸出數據量通常會比輸入有 大幅減少,因此即使不設定 reducer 個數,重設參數 2 還是必要的。

依據 Hadoop 的經驗,可以將參數 2 設定爲 0.95*(集羣中 datanode 個數)。 

九、合併 MapReduce 操作

Multi-group by 是 Hive 的一個非常好的特性,它使得 Hive 中利用中間結果變得非常方便。 例如:

複製代碼
FROM (SELECT a.status, b.school, b.gender FROM status_updates a JOIN profiles b ON (a.userid =
b.userid and a.ds='2009-03-20' ) ) subq1
INSERT OVERWRITE TABLE gender_summary PARTITION(ds='2009-03-20')
SELECT subq1.gender, COUNT(1) GROUP BY subq1.gender
INSERT OVERWRITE TABLE school_summary PARTITION(ds='2009-03-20')
SELECT subq1.school, COUNT(1) GROUP BY subq1.school
複製代碼

上述查詢語句使用了 multi-group by 特性連續 group by 了 2 次數據,使用不同的 group by key。 這一特性可以減少一次 MapReduce 操作

十、合理利用分桶:Bucketing 和 Sampling

Bucket 是指將數據以指定列的值爲 key 進行 hash,hash 到指定數目的桶中。這樣就可以支 持高效採樣了。如下例就是以 userid 這一列爲 bucket 的依據,共設置 32 個 buckets

複製代碼
CREATE TABLE page_view(viewTime INT, userid BIGINT,
 page_url STRING, referrer_url STRING,
 ip STRING COMMENT 'IP Address of the User')
 COMMENT 'This is the page view table'
 PARTITIONED BY(dt STRING, country STRING)
 CLUSTERED BY(userid) SORTED BY(viewTime) INTO 32 BUCKETS
 ROW FORMAT DELIMITED
 FIELDS TERMINATED BY '1'
 COLLECTION ITEMS TERMINATED BY '2'
 MAP KEYS TERMINATED BY '3'
 STORED AS SEQUENCEFILE;
複製代碼

通常情況下,Sampling 在全體數據上進行採樣,這樣效率自然就低,它要去訪問所有數據。 而如果一個表已經對某一列製作了 bucket,就可以採樣所有桶中指定序號的某個桶,這就 減少了訪問量。

如下例所示就是採樣了 page_view 中 32 個桶中的第三個桶的全部數據:

SELECT * FROM page_view TABLESAMPLE(BUCKET 3 OUT OF 32);

如下例所示就是採樣了 page_view 中 32 個桶中的第三個桶的一半數據:

SELECT * FROM page_view TABLESAMPLE(BUCKET 3 OUT OF 64);

 

十一、合理利用分區:Partition 

 Partition 就是分區。分區通過在創建表時啓用 partitioned by 實現,用來 partition 的維度並不 是實際數據的某一列,具體分區的標誌是由插入內容時給定的。當要查詢某一分區的內容時 可以採用 where 語句,形似 where tablename.partition_column = a 來實現。

創建含分區的表

複製代碼
CREATE TABLE page_view(viewTime INT, userid BIGINT,
 page_url STRING, referrer_url STRING,
 ip STRING COMMENT 'IP Address of the User')
PARTITIONED BY(date STRING, country STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '1'
STORED AS TEXTFILE;
複製代碼

載入內容,並指定分區標誌

load data local inpath '/home/hadoop/pv_2008-06-08_us.txt' into table page_view
partition(date='2008-06-08', country='US');

查詢指定標誌的分區內容

SELECT page_views.* FROM page_views
 WHERE page_views.date >= '2008-03-01' AND page_views.date <= '2008-03-31' AND
page_views.referrer_url like '%xyz.com';

 

十二、Join 優化

總體原則:

  1、 優先過濾後再進行 Join 操作,最大限度的減少參與 join 的數據量

  2、 小表 join 大表,最好啓動 mapjoin

  3、 Join on 的條件相同的話,最好放入同一個 job,並且 join 表的排列順序從小到大 

在使用寫有 Join 操作的查詢語句時有一條原則:應該將條目少的表/子查詢放在 Join 操作 符的左邊。原因是在 Join 操作的 Reduce 階段,位於 Join 操作符左邊的表的內容會被加 載進內存,將條目少的表放在左邊,可以有效減少發生 OOM 錯誤的機率。對於一條語句 中有多個 Join 的情況,如果 Join 的條件相同,比如查詢

INSERT OVERWRITE TABLE pv_users
SELECT pv.pageid, u.age FROM page_view p
JOIN user u ON (pv.userid = u.userid)
JOIN newuser x ON (u.userid = x.userid);

 

如果 Join 的 key 相同,不管有多少個表,都會則會合併爲一個 Map-Reduce 任務,而不 是”n”個,在做 OUTER JOIN 的時候也是一樣

如果 join 的條件不相同,比如:

INSERT OVERWRITE TABLE pv_users
 SELECT pv.pageid, u.age FROM page_view p
 JOIN user u ON (pv.userid = u.userid)
 JOIN newuser x on (u.age = x.age);

Map-Reduce 的任務數目和 Join 操作的數目是對應的,上述查詢和以下查詢是等價的

複製代碼
--先 page_view 表和 user 表做鏈接
INSERT OVERWRITE TABLE tmptable
 SELECT * FROM page_view p JOIN user u ON (pv.userid = u.userid);
-- 然後結果表 temptable 和 newuser 表做鏈接
INSERT OVERWRITE TABLE pv_users
 SELECT x.pageid, x.age FROM tmptable x JOIN newuser y ON (x.age = y.age); 
複製代碼

在編寫 Join 查詢語句時,如果確定是由於 join 出現的數據傾斜,那麼請做如下設置:

set hive.skewjoin.key=100000; // 這個是 join 的鍵對應的記錄條數超過這個值則會進行 分拆,值根據具體數據量設置

set hive.optimize.skewjoin=true; // 如果是 join 過程出現傾斜應該設置爲 true 

十三、Group By 優化

 1、Map 端部分聚合

並不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map 端進 行部分聚合,最後在 Reduce 端得出最終結果。

MapReduce 的 combiner 組件參數包括:

set hive.map.aggr = true 是否在 Map 端進行聚合,默認爲 True

set hive.groupby.mapaggr.checkinterval = 100000 在 Map 端進行聚合操作的條目數目

2、使用 Group By 有數據傾斜的時候進行負載均衡

 set hive.groupby.skewindata = true

當 sql 語句使用 groupby 時數據出現傾斜時,如果該變量設置爲 true,那麼 Hive 會自動進行 負載均衡。策略就是把 MR 任務拆分成兩個:第一個先做預彙總,第二個再做最終彙總

在 MR 的第一個階段中,Map 的輸出結果集合會緩存到 maptaks 中,每個 Reduce 做部分聚 合操作,並輸出結果,這樣處理的結果是相同 Group By Key 有可能被分發到不同的 Reduce 中, 從而達到負載均衡的目的;第二個階段 再根據預處理的數據結果按照 Group By Key 分佈到 Reduce 中(這個過程可以保證相同的 Group By Key 被分佈到同一個 Reduce 中),最後完成 最終的聚合操作。

十四、合理利用文件存儲格式 

創建表時,儘量使用 orc、parquet 這些列式存儲格式,因爲列式存儲的表,每一列的數據在 物理上是存儲在一起的,Hive 查詢時會只遍歷需要列數據,大大減少處理的數據量。

十五、本地模式執行 MapReduce

Hive 在集羣上查詢時,默認是在集羣上 N 臺機器上運行, 需要多個機器進行協調運行,這 個方式很好地解決了大數據量的查詢問題。但是當 Hive 查詢處理的數據量比較小時,其實 沒有必要啓動分佈式模式去執行,因爲以分佈式方式執行就涉及到跨網絡傳輸、多節點協調 等,並且消耗資源。這個時間可以只使用本地模式來執行 mapreduce job,只在一臺機器上 執行,速度會很快。啓動本地模式涉及到三個參數:

set hive.exec.mode.local.auto=true 是打開 hive 自動判斷是否啓動本地模式的開關,但是隻 是打開這個參數並不能保證啓動本地模式,要當 map 任務數不超過

hive.exec.mode.local.auto.input.files.max 的個數並且 map 輸入文件大小不超過

hive.exec.mode.local.auto.inputbytes.max 所指定的大小時,才能啓動本地模式。

十六、並行化處理

一個 hive sql 語句可能會轉爲多個 mapreduce Job,每一個 job 就是一個 stage,這些 job 順序 執行,這個在 cli 的運行日誌中也可以看到。但是有時候這些任務之間並不是是相互依賴的, 如果集羣資源允許的話,可以讓多個並不相互依賴 stage 併發執行,這樣就節約了時間,提 高了執行速度,但是如果集羣資源匱乏時,啓用並行化反倒是會導致各個 job 相互搶佔資源 而導致整體執行性能的下降。啓用並行化:

set hive.exec.parallel=true;

set hive.exec.parallel.thread.number=8; //同一個 sql 允許並行任務的最大線程數

十七、設置壓縮存儲

1、壓縮的原因

Hive 最終是轉爲 MapReduce 程序來執行的,而 MapReduce 的性能瓶頸在於網絡 IO 和 磁盤 IO,要解決性能瓶頸,最主要的是減少數據量,對數據進行壓縮是個好的方式。壓縮 雖然是減少了數據量,但是壓縮過程要消耗 CPU 的,但是在 Hadoop 中, 往往性能瓶頸不 在於 CPU,CPU 壓力並不大,所以壓縮充分利用了比較空閒的 CPU

2、常用壓縮方法對比

各個壓縮方式所對應的 Class 類:

3、壓縮方式的選擇

壓縮比率

壓縮解壓縮速度

是否支持 Split

4、壓縮使用

Job 輸出文件按照 block 以 GZip 的方式進行壓縮:

set mapreduce.output.fileoutputformat.compress=true // 默認值是 false

set mapreduce.output.fileoutputformat.compress.type=BLOCK // 默認值是 Record

set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.GzipCodec // 默認值是 org.apache.hadoop.io.compress.DefaultCodec

Map 輸出結果也以 Gzip 進行壓縮:

set mapred.map.output.compress=true

set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.GzipCodec // 默認值是 org.apache.hadoop.io.compress.DefaultCodec 

對 Hive 輸出結果和中間都進行壓縮:

set hive.exec.compress.output=true // 默認值是 false,不壓縮

set hive.exec.compress.intermediate=true // 默認值是 false,爲 true 時 MR 設置的壓縮才啓用

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章