Hive性能優化祕籍

1. Fetch抓取

Fetch 抓取是指,Hive 中對某些情況的查詢可以不必使用 MapReduce 計算。例如:SELECT * FROM employees; 在這種情況下,Hive 可以簡單地讀取 employee 對應的存儲目錄下的文件,然後輸出查詢結果到控制檯。

  • 修改 hive-default.xml.template 文件中 hive.fetch.task.conversion 的值爲 more(默認是more),該屬性修改爲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 的命令行中輸入set hive.fetch.task.conversion=more該方法只有在當前命令行有效,上一種方式是全局的)。

2. 本地模式

大多數的 Hadoop Job 是需要 Hadoop 提供的完整的可擴展性來處理大數據集的。不過,有時 Hive 的輸入數據量是非常小的。在這種情況下,爲查詢觸發執行任務消耗的時間可能會比實際 job 的執行時間要多的多。對於大多數這種情況,Hive 可以通過本地模式在單臺機器上處理所有的任務。對於小數據集,執行時間可以明顯被縮短。

  • 修改 hive-default.xml.template 文件中 hive.exec.mode.local.auto 的值爲 true(默認false),來讓 Hive 在適當的時候自動啓動這個優化。
  • 修改 hive-default.xml.template 文件中 hive.exec.mode.local.auto.inputbytes.max的值爲 500000000(默認134217728,即128M)設置 local mr 的最大數據量,當輸入數據量小於這個值時採用 local mr 的方式。
  • 修改 hive-default.xml.template 文件中 hive.exec.mode.local.auto.input.files.max的值爲 10(默認爲4),設置local mr的最大輸入文件個數,當輸入文件個數小於這個值時採用local mr的方式。

PS:這個在本地測試的時候最常用,查詢數據可以說是瞬間完成。

3. 小表 Join 大表

將key相對分散,並且數據量小的表放在 join 的左邊,這樣可以有效減少內存溢出錯誤發生的機率;再進一步,可以使用 Group 讓小的維度表(1000條以下的記錄條數)先進內存。在 map 端完成 reduce。

實際測試發現:新版的 hive 已經對小表 join大表和大表 join小表進行了優化。小表放在左邊和右邊已經沒有明顯區別。

4. 大表 Join 大表

4.1. 空key過濾

有時 join 超時是因爲某些 key 對應的數據太多,而相同 key 對應的數據都會發送到相同的 reducer 上,從而導致內存不夠。此時我們應該仔細分析這些異常的 key,很多情況下,這些 key 對應的數據是異常數據,我們需要在SQL語句中進行過濾。

  • 未優化::select n.* from nullidtable n left join ori o on n.id = o.id;
  • 優化:select n.* from (select * from nullidtable where id is not null ) n left join ori o on n.id = o.id;

4.2. 空key轉換

有時雖然某個 key 爲空對應的數據很多,但是相應的數據不是異常數據,必須要包含在 join 的結果中,此時我們可以表 a 中 key 爲空的字段賦一個隨機的值,使得數據隨機均勻地分不到不同的 reducer 上。

  • 未優化:select n.* from nullidtable n left join ori b on n.id = b.id;
  • 優化:select n.* from nullidtable n full join ori o on case when n.id is null then concat('hive', rand()) else n.id end = o.id;

5. 開啓MapJoin

如果不指定 MapJoin 或者不符合 MapJoin 的條件,那麼 Hive 解析器會將 Join 操作轉換成 Common Join,即:在 Reduce 階段完成 join。容易發生數據傾斜。可以用 MapJoin 把小表全部加載到內存在 map 端進行 join,避免 reducer 處理。

  • 修改 hive-default.xml.template 文件中 hive.auto.convert.join 的值爲 true(默認爲true),設置自動選擇 MapJoin。
  • set hive.mapjoin.smalltable.filesize=25000000設置大表小表的闕值(默認25M以下認爲是小表)

具體底層 MapReduce 編程的時候是通過 DistributedCache 這個類來預先在 maptask 所在的節點加載小表數據到內存來實現的。

6. 啓用Combiner

默認情況下,Map 階段同一 Key 數據分發給一個 reduce,當一個 key 數據過大時就傾斜了。並不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map 端進行部分聚合,最後在 Reduce 端得出最終結果。(其實也就是我們在MapReduce編程時使用的Combiner

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

  • 在 map 端進行聚合操作的數據條數
    set hive.groupby.mapaggr.checkinterval=100000

  • 有數據傾斜的時候進行負載均衡(默認是false)
    set hive.groupby.skewindata=true

    當選項設定爲 true,生成的查詢計劃會有兩個MR Job。第一個MR Job中,Map的輸出結果會隨機分佈到Reduce中,每個Reduce做部分聚合操作,並輸出結果,這樣處理的結果是相同的Group By Key有可能被分發到不同的Reduce中,從而達到負載均衡的目的;第二個MR Job再根據預處理的數據結果按照Group By Key分佈到Reduce中(這個過程可以保證相同的Group By Key被分佈到同一個Reduce中),最後完成最終的聚合操作。

7. 大數據量使用GroupBy,Count去重統計

數據量小的時候我們一般使用Count(distinct)進行去重統計,數據量大的情況下,由於COUNT DISTINCT 操作需要用一個 Reduce Task 來完成,這一個 Reduce 需要處理的數據量太大,就會導致整個 Job 很難完成,一般 COUNT DISTINCT 使用先 GROUP BY 再 COUNT 的方式替換。

  • 小數據量:select count(distinct id) from bigtable
  • 大數據量:select count(id) from (select id from bigtable group by id) a

8. 儘量避免笛卡爾積

儘量避免笛卡爾積,join 的時候不加 on 條件,或者無效的 on 條件,Hive 只能使用1個reducer 來完成笛卡爾積,會導致 reduce task 的壓力過大。

反例:select empId, eName, dName from a join b
正例:select empId, eName, dName from a join b on a.dId = b.id

9. 行列過濾

  • 列處理:在SELECT中,只拿需要的列,如果有,儘量使用分區過濾,少用 select *。

  • 行處理:在分區剪裁中,當使用外關聯時,如果將副表的過濾條件寫在Where後面,那麼就會先全表關聯,之後再過濾。

  • 未優化:select o.id from bigtable b join ori o on o.id = b.id where o.id <= 10;

  • 優化:select b.id from bigtable b join (select id from ori where id <= 10 ) o on b.id = o.id;

10. 使用分區表

關係型數據庫中,對分區表 Insert 數據時候,數據庫自動會根據分區字段的值,將數據插入到相應的分區中,Hive 中也提供了類似的機制,即動態分區(Dynamic Partition),只不過,使用 Hive 的動態分區,需要進行相應的配置。

  • 開啓動態分區功能(默認true,開啓)
    set hive.exec.dynamic.partition=true
    
  • 設置爲非嚴格模式(動態分區的模式,默認strict,表示必須指定至少一個分區爲靜態分區,nonstrict模式表示允許所有的分區字段都可以使用動態分區)。
    set hive.exec.dynamic.partition.mode=nonstrict
    
  • 在所有執行MR的節點上,最大一共可以創建多少個動態分區。
    set hive.exec.max.dynamic.partitions=1000
    
  • 在每個執行MR的節點上,最大可以創建多少個動態分區。該參數需要根據實際的數據來設定。比如:源數據中包含了一年的數據,即day字段有365個值,那麼該參數就需要設置成大於365,如果使用默認值100,則會報錯。
    set hive.exec.max.dynamic.partitions.pernode=100
    
  • 整個MR Job中,最大可以創建多少個HDFS文件。
    set hive.exec.max.created.files=100000
    
  • 當有空分區生成時,是否拋出異常。一般不需要設置。
    set hive.error.on.empty.partition=false
    

11. 避免數據傾斜:合理設置map數

11.1. 問題

  1. 通常情況下,作業會通過 input 的目錄產生一個或者多個 map 任務。

    主要的決定因素有:input 的文件總個數,input 的文件大小,集羣設置的文件塊大小。

  2. 是不是map數越多越好?

    答案是否定的。如果一個任務有很多小文件(遠遠小於塊大小128m),則每個小文件也會被當做一個塊,用一個map任務來完成,而一個map任務啓動和初始化的時間遠遠大於邏輯處理的時間,就會造成很大的資源浪費。而且,同時可執行的map數是受限的。

  3. 是不是保證每個map處理接近128m的文件塊,就高枕無憂了?

    答案也是不一定。比如有一個127m的文件,正常會用一個map去完成,但這個文件只有一個或者兩個小字段,卻有幾千萬的記錄,如果map處理的邏輯比較複雜,用一個map任務去做,肯定也比較耗時。

針對上面的問題 2 和 3,我們需要採取兩種方式來解決,即減少 map 數和增加 map 數:

11.2. 解決方案

  1. 小文件合併來減少 maptask 的個數。

    在map執行前合併小文件,減少map數:CombineHiveInputFormat 具有對小文件進行合併的功能(系統默認的格式)。HiveInputFormat 沒有對小文件合併功能。

    set hive.input.format= org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
    

    PS:在底層 MapReduce 編程的時候我們指定的 InputFormat 類型是:CombineTextInputFormat

  2. 複雜文件增加Map數。

    當input的文件都很大,任務邏輯複雜,map 執行非常慢的時候,可以考慮增加 Map數,來使得每個 map 處理的數據量減少,從而提高任務的執行效率。

    原理: 根據 MapReduce 源碼中計算切片大小的邏輯:computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M我們通過調整 maxSize 的值讓它小於 blocksize(128m)大於 minsize 的值(1B)就可以使每一個切片的大小變小,而 maptask 的個數是由切片的個數決定的,因此,maptask 的個數就會增大,從而能夠更高效的執行復雜的業務邏輯。

    解決: set mapreduce.input.fileinputformat.split.maxsize=100;

12. 避免數據傾斜:合理設置reduce數

12.1. 調整reducer數方法一

  1. 每個Reduce處理的數據量默認是256MB
    set hive.exec.reducers.bytes.per.reducer=256000000

  2. 每個任務最大的reduce數,默認爲1009
    set hive.exec.reducers.max=1009

  3. 計算reducer數的公式
    N=min(參數2,總輸入數據量/參數1)

12.2. 調整reducer數方法二

  • 在hadoop的mapred-default.xml文件中修改

  • 直接修改:set mapreduce.job.reduces = 15;

12.3. reduce個數並不是越多越好

  1. 過多的啓動和初始化reduce也會消耗時間和資源

  2. 另外,有多少個reduce,就會有多少個輸出文件,如果生成了很多個小文件,那麼如果這些小文件作爲下一個任務的輸入,則也會出現小文件過多的問題。
    在設置reduce個數的時候也需要考慮這兩個原則:處理大數據量利用合適的reduce數;使單個reduce任務處理數據量大小要合適。

13. 開啓任務並行執行

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

  • 開啓並行執行:set set hive.exec.parallel=true;
  • 設置並行度:set hive.exec.parallel.thread.number=16;(同一個 hql 允許的最大並行度,默認爲8)

14. Hive的查詢開啓嚴格模式

Hive提供了一個嚴格模式,可以防止用戶執行那些可能意向不到的不好的影響的查詢。通過設置屬性hive.mapred.mode值爲默認是非嚴格模式nonstrict 。開啓嚴格模式需要修改hive.mapred.mode值爲strict,開啓嚴格模式可以禁止3種類型的查詢。

set hive.mapred.mode=strict
<property>
    <name>hive.mapred.mode</name>
    <value>strict</value>
    <description>
      The mode in which the Hive operations are being performed. 
      In strict mode, some risky queries are not allowed to run. They include:
        Cartesian Product.
        No partition being picked up for a query.
        Comparing bigints and strings.
        Comparing bigints and doubles.
        Orderby without limit.
    </description>
</property>
  1. 對於分區表,除非where語句中含有分區字段過濾條件來限制範圍,否則不允許執行。換句話說,就是用戶不允許掃描所有分區。進行這個限制的原因是,通常分區表都擁有非常大的數據集,而且數據增加迅速。沒有進行分區限制的查詢可能會消耗令人不可接受的巨大資源來處理這個表。

  2. 對於使用了order by語句的查詢,要求必須使用limit語句。因爲order by爲了執行排序過程會將所有的結果數據分發到同一個Reducer中進行處理,強制要求用戶增加這個LIMIT語句可以防止Reducer額外執行很長一段時間。

  3. 限制笛卡爾積的查詢。對關係型數據庫非常瞭解的用戶可能期望在執行JOIN查詢的時候不使用ON語句而是使用where語句,這樣關係數據庫的執行優化器就可以高效地將WHERE語句轉化成那個ON語句。不幸的是,Hive並不會執行這種優化,因此,如果表足夠大,那麼這個查詢就會出現不可控的情況。

15. JVM重用

JVM 重用是 Hadoop 調優參數的內容,其對 Hive 的性能具有非常大的影響,特別是對於很難避免小文件的場景或 task 特別多的場景,這類場景大多數執行時間都很短。

Hadoop 的默認配置通常是使用派生 JVM 來執行 map 和 Reduce 任務的。這時 JVM 的啓動過程可能會造成相當大的開銷,尤其是執行的 job 包含有成百上千 task 任務的情況。JVM 重用可以使得JVM實例在同一個 job 中重新使用 N 次。N 的值可以在 Hadoop 的 mapred-site.xml 文件中進行配置。通常在 10-20 之間,具體多少需要根據具體業務場景測試得出。

<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>

這個功能的缺點是,開啓 JVM 重用將一直佔用使用到的 task 插槽,以便進行重用,直到任務完成後才能釋放。如果某個“不平衡的” job 中有某幾個 reduce task 執行的時間要比其他 Reduce task 消耗的時間多的多的話,那麼保留的插槽就會一直空閒着卻無法被其他的 job 使用,直到所有的 task 都結束了纔會釋放。

PS:其實這種做法就類似與線程池,數據庫連接池這種資源池管理資源的方式。

16. 推測執行

在分佈式集羣環境下,因爲程序 Bug(包括 Hadoop 本身的 bug),負載不均衡或者資源分佈不均等原因,會造成同一個作業的多個任務之間運行速度不一致,有些任務的運行速度可能明顯慢於其他任務(比如一個作業的某個任務進度只有50%,而其他所有任務已經運行完畢),則這些任務會拖慢作業的整體執行進度。爲了避免這種情況發生, Hadoop 採用了推測執行(Speculative Execution)機制,它根據一定的法則推測出“拖後腿”的任務,併爲這樣的任務啓動一個備份任務,讓該任務與原始任務同時處理同一份數據,並最終選用最先成功運行完成任務的計算結果作爲最終結果。

設置開啓推測執行參數:Hadoop 的 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 本身也提供了配置項來控制 map-reduce 的推測執行:

修改 hive-default.xml.template 文件中的 hive.mapred.reduce.tasks.speculative.execution 值爲 true:

 <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的話,那麼啓動推測執行造成的浪費是非常巨大大。

Hive相關

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