總結了一下在以往工作中,對於`Hive SQL`調優的一些實際應用,是日常積累的一些優化技巧,如有出入,歡迎在評論區留言探討~ ...
總結了一下在以往工作中,對於Hive SQL
調優的一些實際應用,是日常積累的一些優化技巧,如有出入,歡迎在評論區留言探討~
EXPLAIN 查看執行計劃
建表優化
分區
- 分區表基本操作,partitioned
- 二級分區
- 動態分區
分桶
- 分桶表基本操作,clustered
- 分桶表主要是抽樣查詢,找出具有代表性的結果
選擇合適的文件格式和壓縮格式
- LZO,拉茲羅
- Snappy
- 壓縮速度快,壓縮比高
HiveSQL語法優化
單表查詢優化
-
列裁剪和分區裁剪,全表和全列掃描效率都很差,生產環境絕對不要使用
SELECT *
,所謂列裁剪就是在查詢時只讀取需要的列,分區裁剪就是只讀取需要的分區- 與列裁剪優化相關的配置項是
hive.optimize.cp
,預設是true - 與分區裁剪優化相關的則是
hive.optimize.pruner
,預設是true - 在
HiveSQL
解析階段對應的則是ColumnPruner
邏輯優化器
- 與列裁剪優化相關的配置項是
-
Group By 配置調整,
map
階段會把同一個key
發給一個reduce
,當一個key
過大時就傾斜了,可以開啟map
端預聚合,可以有效減少shuffle
數據量並# 是否在map端聚合,預設為true set hive.map.aggr = true; # 在map端聚合的條數 set hive.groupby.mapaggr.checkintervel = 100000; # 在數據傾斜的時候進行均衡負載(預設是false),開啟後會有 兩個`mr任務`。 # 當選項設定為true時,第一個 `mr任務` 會將map輸出的結果隨機分配到`reduce`, # 每個`reduce`會隨機分佈到`reduce`上,這樣的處理結果是會使相同的`group by key`分到不同的`reduce`上。 # 第二個 `mr任務` 再根據預處理的結果按`group by key`分到`reduce`上, # 保證相同`group by key`的數據分到同一個`reduce`上。 # *切記!!!* # 這樣能解決數據傾斜,但是不能讓運行速度更快 # 在數據量小的時候,開始數據傾斜負載均衡可能反而會導致時間變長 # 配置項畢竟是死的,單純靠它有時不能根本上解決問題 # 因此還是建議自行瞭解數據傾斜的細節,並優化查詢語句 set hive.groupby.skewindata = true;
-
Vectorization,矢量計算技術,通過設置批處理的增量大小為1024行單次來達到比單行單次更好的效率
# 開啟矢量計算 set hive.vectorized.execution.enabled = true; # 在reduce階段開始矢量計算 set hive.vectorized.execution.reduce.enabled = true;
-
多重模式,一次讀取多次插入,同一張表的插入操作優化成先
from table
再insert
-
in/exists或者join用
left semi join
代替(為什麼替代擴展一下~)
多表查詢優化
-
CBO優化,成本優化器,代價最小的執行計劃就是最好的執行計劃
- join的時候表的順序關係,前面的表都會被載入到記憶體中,後面的表進行磁碟掃描
- 通過
hive.cbo.enable
,自動優化hivesql中多個join的執行順序 - 可以通過查詢一下參數,這些一般都是true,無需修改
set hive.cbo.enable = true; set hive.compute.query.using.stats = true; set hive.stats.fetch.column.stats = true; set hive.stats.fetch.partition.stats = true;
-
謂詞下推(非常關鍵的一個優化),將
sql
語句中的where
謂詞邏輯都儘可能提前執行,減少下游處理的數據量,
在關係型資料庫如MySQL中,也有謂詞下推(Predicate Pushdown,PPD)
的概念,
它就是將sql
語句中的where
謂詞邏輯都儘可能提前執行,減少下游處理的數據量# 這個設置是預設開啟的 # 如果關閉了但是cbo開啟,那麼關閉依然不會生效 # 因為cbo會自動使用更為高級的優化計劃 # 與它對應的邏輯優化器是PredicatePushDown # 該優化器就是將OperatorTree中的FilterOperator向上提 set hive.optimize.pdd = true; # 舉個例子 # 對forum_topic做過濾的where語句寫在子查詢內部,而不是外部 select a.uid,a.event_type,b.topic_id,b.title from calendar_record_log a left outer join ( select uid,topic_id,title from forum_topic where pt_date = 20220108 and length(content) >= 100 ) b on a.uid = b.uid where a.pt_date = 20220108 and status = 0;
-
Map Join,
map join
是指將join
操作兩方中比較小的表直接分發到各個map
進程的記憶體中,在map
中進行join
的操作。
map join
特別適合大小表join
的情況,Hive會將build table
和probe table
在map
端直接完成join
過程,消滅了reduce
,減少shuffle
,所以會減少開銷set hive.auto.convert.join = true
,配置開啟,預設是true- 註意!!! 如果執行
小表join大表
,小表作為主連接的主表,所有數據都要寫出去,此時會走reduce
階段,mapjoin
會失效 大表join小表
不受影響,上一條的原因主要是因為小表join大表
的時候,map
階段不知道reduce
的結果其他reduce
是否有,- 所以必須在最後
reduce
聚合的時候再處理,就產生了reduce
的開銷
# 舉個例子 # 在最常見的`hash join`方法中,一般總有一張相對小的表和一張相對大的表, # 小表叫`build table`,大表叫`probe table` # Hive在解析帶join的SQL語句時,會預設將最後一個表作為`probe table`, # 將前面的表作為`build table`並試圖將它們讀進記憶體 # 如果表順序寫反,`probe table`在前面,引發`OOM(記憶體不足)`的風險就高了 # 在維度建模數據倉庫中,事實表就是`probe table`,維度表就是`build table` # 假設現在要將日曆記錄事實表和記錄項編碼維度表來`join` select a.event_type,a.event_code,a.event_desc,b.upload_time from calendar_event_code a inner join ( select event_type,upload_time from calendar_record_log where pt_date = 20220108 ) b on a.event_type = b.event_type;
-
Map Join,大表和大表的
MapReduce任務
,可以使用SMB Join
- 直接join耗時會很長,但是根據某欄位分桶後,兩個大表每一個桶就是一個小文件,兩個表的每個小文件的分桶欄位都應該能夠一一對應(hash值取模的結果)
- 總結就是分而治之,註意兩個大表的分桶欄位和數量都應該保持一致
set hive.optimize.bucketmapjoin = true; set hive.optimeize.bucketmapjoin.sortedmerge = true; hive.input.format = org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;
-
多表join時key相同,這種情況會將多個
join
合併為一個mr 任務
來處理# 舉個例子 # 如果下麵兩個join的條件不相同 # 比如改成a.event_code = c.event_code # 就會拆成兩個MR job計算 select a.event_type,a.event_code,a.event_desc,b.upload_time from calendar_event_code a inner join ( select event_type,upload_time from calendar_record_log where pt_date = 20220108 ) b on a.event_type = b.event_type inner join ( select event_type,upload_time from calendar_record_log_2 where pt_date = 20220108 ) c on a.event_type = c.event_type;
-
笛卡爾積,在生產環境中嚴禁使用
其他查詢優化
-
Sort By 代替 Order By,HiveQL中的
order by
與其他sql
方言中的功能一樣,就是將結果按某欄位全局排序,這會導致所有map
端數據都進入一個reducer
中,
在數據量大時可能會長時間計算不完。如果使用sort by
,那麼還是會視情況啟動多個reducer進行排序
,並且保證每個reducer
內局部有序。
為了控制map
端數據分配到reducer
的key
,往往還要配合distribute by
一同使用,如果不加distribute by
的話,map
端數據就會隨機分配到reducer
# 舉個例子 select uid,upload_time,event_type,record_data from calendar_record_log where pt_date >= 20220108 and pt_date <= 20220131 distribute by uid sort by upload_time desc,event_type desc;
-
Group By代替Distinct,當要統計某一列的去重數時,如果數據量很大,
count(distinct)
就會非常慢,原因與order by
類似,
count(distinct)
邏輯只會有很少的reducer
來處理。但是這樣寫會啟動兩個mr任務
(單純distinct只會啟動一個),
所以要確保數據量大到啟動mr任務
的overhead
遠小於計算耗時,才考慮這種方法,當數據集很小或者key
的傾斜比較明顯時,group by
還可能會比distinct
慢
數據傾斜
註意要和數據過量的情況區分開,數據傾斜是大部分任務都已經執行完畢,但是某一個任務或者少數幾個任務,一直未能完成,甚至執行失敗,
而數據過量,是大部分任務都執行的很慢,這種情況需要通過擴充執行資源的方式來加快速度,大數據編程不怕數據量大,就怕數據傾斜,一旦數據傾斜,嚴重影響效率
單表攜帶了 Group By 欄位的查詢
- 任務中存在
group by
操作,同時聚合函數為count
或sum
,單個key
導致的數據傾斜可以這樣通過設置開啟map
端預聚合參數的方式來處理# 是否在map端聚合,預設為true set hive.map.aggr = true; # 在map端聚合的條數 set hive.groupby.mapaggr.checkintervel = 100000; # 有數據傾斜的時候開啟負載均衡,這樣會生成兩個mr任務 set hive.groupby.skewindata = true;
- 任務中存在
group by
操作,同時聚合函數為count
或sum
,多個key
導致的數據傾斜可以通過增加reduce
的數量來處理- 增加分區可以減少不同分區之間的數據量差距,而且增加的分區時候不能是之前分區數量的倍數,不然會導致取模結果相同繼續分在相同分區
- 第一種修改方式
# 每個reduce處理的數量 set hive.exec.reduce.bytes.per.reducer = 256000000; # 每個任務最大的reduce數量 set hive.exec.reducers.max = 1009; # 計算reducer數的公式,根據任務的需要調整每個任務最大的reduce數量 N = min(設置的最大數,總數量數/每個reduce處理的數量)
- 第二種修改方式
# 在hadoop的mapred-default.xml文件中修改 set mapreduce.job.reduces = 15;
兩表或多表的 join 關聯時,其中一個表較小,但是 key 集中
- 設置參數增加
map
數量# join的key對應記錄條數超過該數量,會進行分拆 set hive.skewjoin.key = 1000; # 並設置該參數為true,預設是false set hive.optimize.skewjoin = true; # 上面的參數如果開啟了會將計算數量超過閾值的key寫進臨時文件,再啟動另外一個任務做map join # 可以通過設置這個參數,控制第二個任務的mapper數量,預設10000 set hive.skewjoin.mapjoin.map.tasks = 10000;
- 使用
mapjoin
,減少reduce
從根本上解決數據傾斜,參考HiveSQL語法優化 -> 多表查詢優化 -> Map Join,大表和大表的MapReduce任務,SMB Join
兩表或多表的 join 關聯時,有 Null值 或 無意義值
這種情況很常見,比如當事實表是日誌類數據
時,往往會有一些項沒有記錄到,我們視情況會將它置為null
,或者空字元串
、-1
等,
如果缺失的項很多,在做join
時這些空值就會非常集中,拖累進度,因此,若不需要空值數據,就提前寫where語句過濾掉,
需要保留的話,將空值key用隨機方式打散,例如將用戶ID為null的記錄隨機改為負值:
select a.uid,a.event_type,b.nickname,b.age
from (
select
(case when uid is null then cast(rand()*-10240 as int) else uid end) as uid,
event_type from calendar_record_log
where pt_date >= 20220108
) a left outer join (
select uid,nickname,age from user_info where status = 4
) b on a.uid = b.uid;
兩表或多表的 join 關聯時,數據類型不統一
比如int
類型和string
類型進行關聯,關聯時候以小類型作為分區,這裡int
、string
會到一個reduceTask
中,如果數據量多,會造成數據傾斜
# 可以通過轉換為同一的類型來處理
cast(user.id as string)
單獨處理傾斜key
這其實是上面處理空值方法的拓展,不過傾斜的key
變成了有意義的,一般來講傾斜的key
都很少,我們可以將它們抽樣出來,
對應的行單獨存入臨時表中,然後打上一個較小的隨機數首碼(比如0~9
),最後再進行聚合
Hive Job 優化
Hive Map 優化
Map數量多少的影響
- Map數過大
map
階段輸出文件太小,產生大量小文件- 初始化和創建
map
的開銷很大
- Map數太小
- 文件處理或查詢併發度小,
Job
執行時間過長 - 大量作業時,容易堵塞集群
- 文件處理或查詢併發度小,
控制Map數的原則
根據實際情況,控制map
數量需要遵循兩個原則
- 第一是使大數據量利用合適的
map
數 - 第二是使單個
map
任務處理合適的數據量
複雜文件適當增加Map數
- 當
input
的文件都很大,任務邏輯複雜,map
執行非常慢的時候,可以考慮增加map
數,來使得每個map
處理的數據量減少,從而提高任務的執行效率 - 那麼如何增加
map
的數量呢?在map
階段,文件先被切分成split
塊,而後每一個split
切片對應一個Mapper任務
,
FileInputFormat
這個類先對輸入文件進行邏輯上的劃分,以128m
為單位,將原始數據從邏輯上分割成若幹個split
,每個split
切片對應一個mapper任務
,
所以說減少切片的大小就可增加map
數量 - 可以依據公式計算
computeSliteSize(Math.max(minSize, Math.min(maxSize, blockSize))) = blockSize = 128m
- 執行語句:
set mapreduce.input.fileinputformat.split.maxsize = 100;
小文件進行合併減少Map數
為什麼要進行小文件合併?因為如果一個任務有很多小文件(遠遠小於塊大小128m),則每個小文件也會被當做一個塊,用一個map
任務來完成,
而一個map
任務啟動和初始化的時間遠遠大於邏輯處理的時間,就會造成很大的資源浪費,同時可執行的map
數是受限的
兩種方式合併小文件
- 在
Map執行前
合併小文件,減少map
數量// 每個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任務執行結束時
合併小文件,減少小文件輸出// 設置map端輸出進行合併,預設為true set hive.merge.mapfiles = true; // 設置reduce端輸出進行合併,預設為false set hive.merge.mapredfiles = true; // 設置合併文件的大小,預設是256 set hive.merge.size.per.task = 256 * 1000 * 1000; // 當輸出文件的平均大小小於該值時,啟動一個獨立的`MapReduce任務`進行文件`merge`。 set hive.merge.smallfiles.avgsize = 16000000;
Map端預聚合減少Map數量
- 相當於在
map
端執行combiner
,執行命令:set hive.map.aggr = true;
combiners
是對map
端的數據進行適當的聚合,其好處是減少了從map
端到reduce
端的數據傳輸量- 其作用的本質,是將
map
計算的結果進行二次聚合,使Key-Value<List>
中List
的數據量變小,從而達到減少數據量的目的
推測執行
- 在分散式集群環境下,因為程式Bug(包括Hadoop本身的bug),負載不均衡或者資源分佈不均等原因,會造成同一個作業的多個任務之間運行速度不一致,
有些任務的運行速度可能明顯慢於其他任務(比如一個作業的某個任務進度只有50%,而其他所有任務已經運行完畢),則這些任務會拖慢作業的整體執行進度 - Hadoop採用了
推測執行(Speculative Execution)
機制,它根據一定的法則推測出拖後腿的任務,併為這樣的任務啟動一個備份任務,
讓該任務與原始任務同時處理同一份數據,並最終選用最先成功運行完成任務的計算結果作為最終結果 - 執行命令:
set mapred.reduce.tasks.speculative.execution = true; # 預設是true
- 當然,如果用戶對於運行時的偏差非常敏感的話,那麼可以將這些功能關閉掉,如果用戶因為輸入數據量很大而需要執行長時間的
map task
或者reduce task
的話,
那麼啟動推測執行造成的浪費是非常巨大的
合理控制Map數量的實際案例
假設一個SQL任務:
SELECT COUNT(1)
FROM fx67ll_alarm_count_copy
WHERE alarm_date = "2021-01-08";
該任務的輸入目錄inputdir
是:/group/fx67ll_data/fx67ll_data_etl/date/fx67ll_alarm_count_copy/alarm_date=2021-01-08
,共有194個文件,
其中很多是遠遠小於128m
的小文件,總大小約9G
,正常執行會用194個Map任務
,map
總共消耗的計算資源:SLOTS_MILLIS_MAPS= 610,023
通過在Map執行前合併小文件,減少Map數
# 前面三個參數確定合併文件塊的大小
# 大於文件塊大小128m的,按照128m來分隔
# 小於128m,大於100m的,按照100m來分隔
# 把那些小於100m的(包括小文件和分隔大文件剩下的),進行合併,最終生成了74個塊
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= 323,098
,對於這個簡單SQL任務,執行時間上可能差不多,但節省了一半的計算資源
再假設這樣一個SQL任務:
SELECT data_fx67ll,
COUNT(1),
COUNT(DISTINCT id),
SUM(CASE WHEN …),
SUM(CASE WHEN …),
SUM(…)
FROM fx67ll_device_info_zs
GROUP data_fx67ll
如果表fx67ll_device_info_zs
只有一個文件,大小為120m
,但包含幾千萬的記錄,如果用1個map
去完成這個任務,肯定是比較耗時的,
這種情況下,我們要考慮將這一個文件合理的拆分成多個
增加Reduce數量,來增加Map數量
set mapred.reduce.tasks=10;
CREATE TABLE fx67ll_device_info_zs_temp
AS
SELECT *
FROM fx67ll_device_info_zs
DISTRIBUTE BY RAND(123);
這樣會將fx67ll_device_info_zs
表的記錄,隨機的分散到包含10個文件的fx67ll_device_info_zs_temp
表中,
再用fx67ll_device_info_zs_temp
代替上面sql
中的fx67ll_device_info_zs
表,
則會用10個map
任務去完成,每個map
任務處理大於12m(幾百萬記錄)
的數據,效率肯定會好很多
Hive Reduce 優化
Reduce數量多少的影響
- 同
map
一樣,啟動和初始化reduce
也會消耗時間和資源 - 另外,有多少個
reduce
,就會有多少個輸出文件,如果生成了很多個小文件,那麼如果這些小文件作為下一個任務的輸入,則也會出現小文件過多的問題
控制Reduce數的原則
和map
一樣,控制reduce
數量需要遵循兩個原則
- 第一是使大數據量利用合適的
reduce
數 - 第二是使單個
reduce
任務處理合適的數據量
Hive自己如何確定Reduce數
reduce
個數的設定極大影響任務執行效率,不指定reduce
個數的情況下,Hive會猜測確定一個reduce
個數,基於以下兩個設定:
# 每個reduce任務處理的數據量,預設為 1000^3=1G
hive.exec.reducers.bytes.per.reducer
# 每個任務最大的reduce數,預設為999
hive.exec.reducers.max
計算reducer
數的公式很簡單N = min(參數2,總輸入數據量 / 參數1)
即,如果reduce
的輸入(map
的輸出)總大小不超過1G,那麼只會有一個reduce
任務
舉個例子:
SELECT alarm_date,
COUNT(1)
FROM fx67ll_alarm_count_copy
WHERE alarm_date = "2021-01-08"
GROUP BY alarm_date;
該任務的輸入目錄inputdir
是:/group/fx67ll_data/fx67ll_data_etl/date/fx67ll_alarm_count_copy/alarm_date=2021-01-08
,
總大小為9G
多,因此這句有10個reduce
如何調整Reduce數量
註意!!!實際開發中,reduce的個數一般通過程式自動推定,而不人為干涉,因為人為控制的話,如果使用不當很容易造成結果不准確,且降低執行效率
- 通過調整每個
reduce
任務處理的數據量來調整reduce
個數,處理的數據量少了,任務數就多了# 設置每個reduce任務處理的數據量500M,預設是1G set hive.exec.reducers.bytes.per.reducer = 500000000; SELECT alarm_date, COUNT(1) FROM fx67ll_alarm_count_copy WHERE alarm_date = "2021-01-08" GROUP BY alarm_date; 這次有20個reduce
- 直接調整每個
Job
中的最大reduce
數量,過於簡單粗暴,慎用,儘量不要,雖然設置了reduce
的個數看起來好像執行速度變快了,但是實際並不是這樣的# 設置每個任務最大的reduce數為15個,預設為999 set mapred.reduce.tasks = 15; SELECT alarm_date, COUNT(1) FROM fx67ll_alarm_count_copy WHERE alarm_date = "2021-01-08" GROUP BY alarm_date; 這次有15個reduce
推測執行
參考map優化的最後一項
什麼情況下只有一個Reduce
很多時候你會發現任務中不管數據量多大,不管你有沒有設置調整reduce
個數的參數,任務中一直都只有一個reduce
任務,
其實只有一個reduce
任務的情況,除了數據量小於hive.exec.reducers.bytes.per.reducer
參數值的情況外,還有以下原因:
- 沒有
Group By
的彙總,例如:SELECT alarm_date, COUNT(1) FROM fx67ll_alarm_count_copy WHERE alarm_date = "2021-01-08" GROUP BY alarm_date; 寫成 SELECT COUNT(1) FROM fx67ll_alarm_count_copy WHERE alarm_date = "2021-01-08"; 註意避免這樣情況的發生
- 用了
Order by
排序,因為它會對數據進行全局排序,所以數據量特別大的時候效率非常低,儘量避免 - 有笛卡爾積,生產環境必須嚴格避免
Hive 任務整體優化
Fetch抓取
Fetch抓取
是指Hive在某些情況的查詢可以不必使用mr 任務
,例如在執行一個簡單的select * from XX
時,我們只需要簡單的進行抓取對應目錄下的數據即可。
在hive-default.xml.template
中,hive.fetch.task.conversion(預設是morn)
,老版本中預設是minimal
。
該屬性為morn
時,在全局查找,欄位查找,limit查找等都不走mr 任務
本地模式
Hive也可以不將任務提交到集群進行運算,而是直接在一臺節點上處理,因為消除了提交到集群的overhead,所以比較適合數據量很小,且邏輯不複雜的任務。
設置hive.exec.mode.local.auto
為true可以開啟本地模式,但任務的輸入數據總量必須小於hive.exec.mode.local.auto.inputbytes.max(預設值128MB)
,
且mapper數必須小於hive.exec.mode.local.auto.tasks.max(預設值4)
,reducer
數必須為0或1
,才會真正用本地模式執行
並行執行
Hive中互相沒有依賴關係的job
間是可以並行執行的,最典型的就是多個子查詢union all
,
在集群資源相對充足的情況下,可以開啟並行執行,即將參數hive.exec.parallel
設為true,
另外hive.exec.parallel.thread.number
可以設定並行執行的線程數,預設為8,一般都夠用。
註意!!!沒資源無法並行,且數據量小時開啟可能還沒不開啟快,所以建議數據量大時開啟
嚴格模式
要開啟嚴格模式,需要將參數hive.mapred.mode設為strict
。
所謂嚴格模式,就是強制不允許用戶執行3種有風險的sql
語句,一旦執行會直接失敗,這3種語句是:
- 查詢分區表時不限定分區列的語句
- 兩表join產生了笛卡爾積的語句
- 用order by來排序但沒有指定limit的語句
JVM重用
- 主要用於處理小文件過多的時候
- 在
mr 任務
中,預設是每執行一個task
就啟動一個JVM
,如果task
非常小而碎,那麼JVM
啟動和關閉的耗時就會很長 - 可以通過調節參數
mapred.job.reuse.jvm.num.tasks
來重用 - 例如將這個參數設成5,那麼就代表同一個
mr 任務
中順序執行的5個task
可以重覆使用一個JVM
,減少啟動和關閉的開銷,但它對不同mr 任務
中的task
無效
啟用壓縮
壓縮job的中間結果數據和輸出數據,可以用少量CPU時間節省很多空間,壓縮方式一般選擇Snappy,效率最高。
要啟用中間壓縮,需要設定hive.exec.compress.intermediate
為true,
同時指定壓縮方式hive.intermediate.compression.codec
為org.apache.hadoop.io.compress.SnappyCodec
。
另外,參數hive.intermediate.compression.type
可以選擇對塊(BLOCK)
還是記錄(RECORD)
壓縮,BLOCK的壓縮率比較高。
輸出壓縮的配置基本相同,打開hive.exec.compress.output
即可
採用合適的存儲格式
- 在Hive SQL的
create table
語句中,可以使用stored as ...
指定表的存儲格式。
Hive表支持的存儲格式有TextFile
、SequenceFile
、RCFile
、Avro
、ORC
、Parquet
等。
存儲格式一般需要根據業務進行選擇,在我們的實操中,絕大多數表都採用TextFile
與Parquet
兩種存儲格式之一。 TextFile
是最簡單的存儲格式,它是純文本記錄,也是Hive的預設格式,雖然它的磁碟開銷比較大,查詢效率也低,但它更多地是作為跳板來使用。RCFile
、ORC
、Parquet
等格式的表都不能由文件直接導入數據,必須由TextFile
來做中轉。Parquet
和ORC
都是Apache旗下的開源列式存儲格式。列式存儲比起傳統的行式存儲更適合批量OLAP查詢
,並且也支持更好的壓縮和編碼。- 我們選擇
Parquet
的原因主要是它支持Impala查詢引擎
,並且我們對update
、delete
和事務性操作
需求很低。
Hive的小文件
什麼情況下會產生小文件?
- 動態分區插入數據,產生大量的小文件,從而導致map數量劇增
- reduce數量越多,小文件也越多,有多少個reduce,就會有多少個輸出文件,如果生成了很多小文件,那這些小文件作為下一次任務的輸入
- 數據源本身就包含大量的小文件
小文件有什麼樣的危害?
- 從Hive的角度看,小文件會開很多map,一個map開一個java虛擬機jvm去執行,所以這些任務的初始化,啟動,執行會浪費大量的資源,嚴重影響性能
- 在hdfs中,每個小文件對象約占150byte,如果小文件過多會占用大量記憶體,這樣NameNode記憶體容量嚴重製約了集群的擴展
- 每個hdfs上的文件,會消耗128位元組記錄其meta信息,所以大量小文件會占用大量記憶體
如何避免小文件帶來的危害?
從小文件產生的途經就可以從源頭上控制小文件數量
- 使用Sequencefile作為表存儲格式,不要用textfile,在一定程度上可以減少小文件
- 減少reduce的數量(可以使用參數進行控制)
- 少用動態分區,用時記得按distribute by分區
對於已有的小文件
- 使用hadoop archive命令把小文件進行歸檔,採用archive命令不會減少文件存儲大小,只會壓縮NameNode的空間使用
- 重建表,建表時減少reduce數量
我是 fx67ll.com,如果您發現本文有什麼錯誤,歡迎在評論區討論指正,感謝您的閱讀!
如果您喜歡這篇文章,歡迎訪問我的 本文github倉庫地址,為我點一顆Star,Thanks~