參考了多位大佬的博客,做了一個總結,如有侵犯,請及時聯繫我刪除
Hive知識點總結
Hive內部表和外部表的區別
未被external修飾的是內部表(managed table),被external修飾的爲外部表(external table);
-
內部表數據由Hive自身管理,外部表數據由HDFS管理;
-
內部表數據存儲的位置是hive.metastore.warehouse.dir(默認:/user/hive/warehouse),外部表數據的存儲位置由自己制定;
-
刪除內部表會直接刪除元數據(metadata)及存儲數據;刪除外部表僅僅會刪除元數據,HDFS上的文件並不會被刪除;
行存儲和列存儲
行式數據庫存儲在hdfs上式按行進行存儲的,一個block存儲一或多行數據。而列式數據庫在hdfs上則是按照列進行存儲,一個block可能有一列或多列數據。
如果要將數據進行壓縮:
對於行式數據庫,必然按行壓縮,當一行中有多個字段,各個字段對應的數據類型可能不一致,壓縮性能壓縮比就比較差。
對於列式數據庫,必然按列壓縮,每一列對應的是相同數據類型的數據,故列式數據庫的壓縮性能要強於行式數據庫。
Hive靜態分區動態分區
分區的概念
-
Hive的分區方式:由於Hive實際是存儲在HDFS上的抽象,Hive的一個分區名對應HDFS上的一個目錄名,子分區名就是子目錄名,並不是一個實際字段。
分區的好處 -
產生背景:如果一個表中數據很多,我們查詢時就很慢,耗費大量時間,如果要查詢其中部分數據該怎麼辦呢,這是我們引入分區的概念。
-
Partition:分區,每張表中可以加入一個分區或者多個,方便查詢,提高效率;並且HDFS上會有對應的分區目錄:
-
語法:
Hive分區是在創建表的時候用Partitioned by 關鍵字定義的,但要注意,Partitioned by子句中定義的列是表中正式的列,
但是Hive下的數據文件中並不包含這些列,因爲它們是目錄名,真正的數據在分區目錄下。 -
靜態分區和 動態分區的區別
創建表的語法都一樣
靜態分區:加載數據的時候要指定分區的值(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階段優化
- 減少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個塊。
- 增大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階段優化
- 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任務;
- 調整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
- 調整reduce個數方法二:
set mapred.reduce.tasks=15;
select pt,count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’ group by pt;
這次有15個reduce
- reduce個數並不是越多越好;
同map一樣,啓動和初始化reduce也會消耗時間和資源;
另外,有多少個reduce,就會有個多少個輸出文件,如果生成了很多個小文件,那麼如果這些小文件作爲下一個任務的輸入,則也會出現小文件過多的問題;
- 什麼情況下只有一個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的邏輯,找出數據傾斜的原因。