Hive知識總結(參考多人博客)

參考了多位大佬的博客,做了一個總結,如有侵犯,請及時聯繫我刪除

Hive知識點總結

Hive內部表和外部表的區別

未被external修飾的是內部表(managed table),被external修飾的爲外部表(external table);

  1. 內部表數據由Hive自身管理,外部表數據由HDFS管理;

  2. 內部表數據存儲的位置是hive.metastore.warehouse.dir(默認:/user/hive/warehouse),外部表數據的存儲位置由自己制定;

  3. 刪除內部表會直接刪除元數據(metadata)及存儲數據;刪除外部表僅僅會刪除元數據,HDFS上的文件並不會被刪除;

行存儲和列存儲

行式數據庫存儲在hdfs上式按行進行存儲的,一個block存儲一或多行數據。而列式數據庫在hdfs上則是按照列進行存儲,一個block可能有一列或多列數據。
如果要將數據進行壓縮:

對於行式數據庫,必然按行壓縮,當一行中有多個字段,各個字段對應的數據類型可能不一致,壓縮性能壓縮比就比較差。
對於列式數據庫,必然按列壓縮,每一列對應的是相同數據類型的數據,故列式數據庫的壓縮性能要強於行式數據庫。

Hive靜態分區動態分區

分區的概念

  1. Hive的分區方式:由於Hive實際是存儲在HDFS上的抽象,Hive的一個分區名對應HDFS上的一個目錄名,子分區名就是子目錄名,並不是一個實際字段。
    分區的好處

  2. 產生背景:如果一個表中數據很多,我們查詢時就很慢,耗費大量時間,如果要查詢其中部分數據該怎麼辦呢,這是我們引入分區的概念。

  3. Partition:分區,每張表中可以加入一個分區或者多個,方便查詢,提高效率;並且HDFS上會有對應的分區目錄:

  4. 語法:
    Hive分區是在創建表的時候用Partitioned by 關鍵字定義的,但要注意,Partitioned by子句中定義的列是表中正式的列,
    但是Hive下的數據文件中並不包含這些列,因爲它們是目錄名,真正的數據在分區目錄下。

  5. 靜態分區和 動態分區的區別

創建表的語法都一樣

靜態分區:加載數據的時候要指定分區的值(key=value),比較麻煩的是每次插入數據都要指定分區的值,創建多個分區多分區一樣,以逗號分隔。
動態分區:
如果用上述的靜態分區,插入的時候必須首先要知道有什麼分區類型,而且每個分區寫一個load data,太煩人。使用動態分區可解決以上問題,其可以根據查詢得到的數據動態分配到分區裏。其實動態分區與靜態分區區別就是不指定分區目錄,由系統自己選擇。

UDF、UDAF、UDTF的區別:

Hive的SQL還可以通過用戶定義的函數(UDF),用戶定義的聚合(UDAF)和用戶定義的表函數(UDTF)進行擴展。當Hive提供的內置函數無法滿足你的業務處理需要時,此時就可以考慮使用用戶自定義函數(UDF)

  • UDF(User-Defined-Function)一進一出
  • UDAF(User-Defined Aggregation Funcation)聚集函數,多進一出
  • UDTF(User-Defined Table-Generating Functions)一進多出,如lateral view explore()

Hive優化

慎用API

我們知道大數據場景下不害怕數據量大,害怕的是數據傾斜,怎樣避免數據傾斜,找到可能產生數據傾斜的函數尤爲關鍵,數據量較大的情況下,慎用count(distinct),count(distinct)容易產生傾斜問題。

設置合理的Mapreduce的task數量

map階段優化

  1. 減少map數量
假設一個SQL任務:
Select count(1) from popt_tbaccountcopy_meswhere pt = '2012-07-04';
該任務的inputdir :  /group/p_sdo_data/p_sdo_data_etl/pt/popt_tbaccountcopy_mes/pt=2012-07-04
共有194個文件,其中很多事遠遠小於128M的小文件,總大小9G,正常執行會用194個map任務。
Map總共消耗的計算資源:SLOTS_MILLIS_MAPS= 623,020

通過以下方法來在map執行前合併小文件,減少map數:
set mapred.max.split.size=100000000;
set mapred.min.split.size.per.node=100000000;
set mapred.min.split.size.per.rack=100000000;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
再執行上面的語句,用了74個map任務,map消耗的計算資源:SLOTS_MILLIS_MAPS= 333,500
對於這個簡單SQL任務,執行時間上可能差不多,但節省了一半的計算資源。
大概解釋一下,100000000表示100M, 
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;這個參數表示執行前進行小文件合併,
前面三個參數確定合併文件塊的大小,大於文件塊大小128m的,按照128m來分隔,
小於128m,大於100m的,按照100m來分隔,把那些小於100m的(包括小文件和分隔大文件剩下的),進行合併,最終生成了74個塊。
  1. 增大map數量
如何適當的增加map數?
當input的文件都很大,任務邏輯複雜,map執行非常慢的時候,可以考慮增加Map數,
來使得每個map處理的數據量減少,從而提高任務的執行效率。

假設有這樣一個任務:
Select data_desc,
         count(1),
         count(distinct id),
         sum(case when ...),
         sum(case when ...),
         sum(...)
from a group by data_desc

如果表a只有一個文件,大小爲120M,但包含幾千萬的記錄,如果用1個map去完成這個任務,肯定是比較耗時的,
這種情況下,我們要考慮將這一個文件合理的拆分成多個,
這樣就可以用多個map任務去完成。
set mapred.reduce.tasks=10;
create table a_1 as 
select * from a 
distribute by rand(123);

這樣會將a表的記錄,隨機的分散到包含10個文件的a_1表中,再用a_1代替上面sql中的a表,則會用10個map任務去完成。
每個map任務處理大於12M(幾百萬記錄)的數據,效率肯定會好很多。

reduce階段優化

  1. Hive自己如何確定reduce數:

reduce個數的設定極大影響任務執行效率,不指定reduce個數的情況下,Hive會猜測確定一個reduce個數,基於以下兩個設定:

hive.exec.reducers.bytes.per.reducer(每個reduce任務處理的數據量,默認爲1000^3=1G)

hive.exec.reducers.max(每個任務最大的reduce數,默認爲999)

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

即,如果reduce的輸入(map的輸出)總大小不超過1G,那麼只會有一個reduce任務;

  1. 調整reduce個數方法一:

調整hive.exec.reducers.bytes.per.reducer參數的值;

set hive.exec.reducers.bytes.per.reducer=500000000; (500M)

select pt, count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’ group by pt;

這次有20個reduce

  1. 調整reduce個數方法二:

set mapred.reduce.tasks=15;

select pt,count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’ group by pt;

這次有15個reduce

  1. reduce個數並不是越多越好;

同map一樣,啓動和初始化reduce也會消耗時間和資源;

另外,有多少個reduce,就會有個多少個輸出文件,如果生成了很多個小文件,那麼如果這些小文件作爲下一個任務的輸入,則也會出現小文件過多的問題;

  1. 什麼情況下只有一個reduce;

很多時候你會發現任務中不管數據量多大,不管你有沒有調整reduce個數的參數,任務中一直都只有一個reduce任務;其實只有一個reduce任務的情況,除了數據量小於hive.exec.reducers.bytes.per.reducer參數值的情況外,還有以下原因:

  • 沒有group by的彙總,比如把select pt,count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’ group by pt; 寫成select count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’; 這點非常常見,希望大家儘量改寫。
  • 用了Order by
  • 有笛卡爾積。

注意:在設置reduce個數的時候也需要考慮這兩個原則:使大數據量利用合適的reduce數;是單個reduce任務處理合適的數據量;

小文件合併優化

Hdfs不適合小文件存儲,文件數目小,容易在文件存儲端造成瓶頸,影響處理效率。因此,可以通過合併Map和Reduce的結果文件來消除。

用於設置合併的參數有:

  • 是否合併Map輸出文件:hive.merge.mapfiles=true(默認值爲true)
  • 是否合併Reduce端輸出文件:hive.merge.mapredfiles=false(默認值爲false)
  • 合併文件的大小:hive.merge.size.per.task=25610001000(默認值爲256000000)

針對小文件文件及其解決方案

小文件是如何產生的:

  • 動態分區插入數據,產生大量的小文件,從而導致map數量劇增;

  • reduce數量越多,小文件也越多(reduce的個數和輸出文件是對應的);

  • 數據源本身就包含大量的小文件。

    小文件問題的影響:

  • 從Hive的角度看,小文件會開很多map,一個map開一個JVM去執行,所以這些任務的初始化,啓動,執行會浪費大量的資源,嚴重影響性能。

  • 在HDFS中,每個小文件對象約佔150byte,如果小文件過多會佔用大量內存。這樣NameNode內存容量嚴重製約了集羣的擴展。

    小文件問題的解決方案:

從小文件產生的途徑就可以從源頭上控制小文件數量,方法如下:

  • 使用Sequencefile作爲表存儲格式,不要用textfile,在一定程度上可以減少小文件;
  • 減少reduce的數量(可以使用參數進行控制);
  • 少用動態分區,用時記得按distribute by分區;

對於已有的小文件,我們可以通過以下幾種方案解決:

  • 使用hadoop archive命令把小文件進行歸檔;
  • 重建表,建表時減少reduce數量;
  • 通過參數進行調節,設置map/reduce端的相關參數,如下:
//每個Map最大輸入大小(這個值決定了合併後文件的數量) 
set mapred.max.split.size=256000000;    
//一個節點上split的至少的大小(這個值決定了多個DataNode上的文件是否需要合併)  
set mapred.min.split.size.per.node=100000000;  
//一個交換機下split的至少的大小(這個值決定了多個交換機上的文件是否需要合併)    
set mapred.min.split.size.per.rack=100000000;  
//執行Map前進行小文件合併  
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;   

設置map輸出和reduce輸出進行合併的相關參數:
[java] view plain copy
//設置map端輸出進行合併,默認爲true  
set hive.merge.mapfiles = true  
//設置reduce端輸出進行合併,默認爲false  
set hive.merge.mapredfiles = true  
//設置合併文件的大小  
set hive.merge.size.per.task = 256*1000*1000  
//當輸出文件的平均大小小於該值時,啓動一個獨立的MapReduce任務進行文件merge。  
set hive.merge.smallfiles.avgsize=16000000

Sql優化

列裁剪、分區裁剪 :主要是在查詢數據中只讀取所需要用到的列(分區),而忽略其他;

對Sql語句本身的優化

Join優化:條目少的表/子查詢放在Join操作符的左邊(新版的hive已經對小表JOIN大表和大表JOIN小表進行了優化。小表放在左邊和右邊已經沒有明顯區別。)

空key過濾–解決方案

假設日誌中常會出現信息丟失,比如每日約爲20億的全網日誌,其中的user_id爲主鍵,在日誌收集過程中會丟失,出現主鍵爲null的情況,如果取其中的user_id和bmw_users關聯,就會碰到數據傾斜的問題。原因是Hive中,主鍵爲null值的項會被當做相同的Key而分配進同一個計算Map。

① user_id爲空的不參與關聯,子查詢過濾null

SELECT * FROM log a
JOIN bmw_users b ON a.user_id IS NOT NULL AND a.user_id=b.user_id
UNION ALL SELECT * FROM log a WHERE a.user_id IS NULL

② 函數過濾null

SELECT * FROM log a LEFT OUTER
JOIN bmw_users b ON
CASE WHEN a.user_id IS NULL THEN CONCAT('dp_hive', RAND()) ELSE a.user_id END = b.user_id;

壓縮格式

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

常用壓縮方法比較

在這裏插入圖片描述

壓縮格式的使用

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 

JVM重用

JVM重用對hive的性能具有非常大的 影響,特別是對於很難避免小文件的場景或者task特別多的場景,這類場景大多數執行時間都很短。jvm的啓動過程可能會造成相當大的開銷,尤其是執行的job包含有成千上萬個task任務的情況。

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

數據傾斜

表現:任務進度長時間維持在99%(或100%),查看任務監控頁面,發現只有少量(1個或幾個)reduce子任務未完成。因爲其處理的數據量和其他reduce差異過大。

原因:某個reduce的數據輸入量遠遠大於其他reduce數據的輸入量

1)、key分佈不均勻

2)、業務數據本身的特性

3)、建表時考慮不周

4)、某些SQL語句本身就有數據傾斜

關鍵詞 情形 後果
join 其中一個表較小,但是key集中 分發到某一個或幾個Reduce上的數據遠高於平均值
join 大表與大表,但是分桶的判斷字段0值或空值過多 這些空值都由一個reduce處理,非常慢
group by group by 維度過小,某值的數量過多 處理某值的reduce非常耗時
count distinct 某特殊值過多 處理此特殊值reduce耗時
解決方案:

(1)參數調節

set hive.map.aggr=true

set hive.groupby.skewindata=true

(2) 熟悉數據的分佈,優化sql的邏輯,找出數據傾斜的原因。

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