Hive小檔案合併

yycdaizi發表於2015-01-31
Hive的後端儲存是HDFS,它對大檔案的處理是非常高效的,如果合理配置檔案系統的塊大小,NameNode可以支援很大的資料量。但是在資料倉儲中,越是上層的表其彙總程度就越高,資料量也就越小。而且這些表通常會按日期進行分割槽,隨著時間的推移,HDFS的檔案數目就會逐漸增加。

小檔案帶來的問題

關於這個問題的闡述可以讀一讀Cloudera的這篇文章。簡單來說,HDFS的檔案元資訊,包括位置、大小、分塊資訊等,都是儲存在NameNode的記憶體中的。每個物件大約佔用150個位元組,因此一千萬個檔案及分塊就會佔用約3G的記憶體空間,一旦接近這個量級,NameNode的效能就會開始下降了。

此外,HDFS讀寫小檔案時也會更加耗時,因為每次都需要從NameNode獲取元資訊,並與對應的DataNode建立連線。對於MapReduce程式來說,小檔案還會增加Mapper的個數,每個指令碼只處理很少的資料,浪費了大量的排程時間。當然,這個問題可以通過使用CombinedInputFile和JVM重用來解決。

Hive小檔案產生的原因

前面已經提到,彙總後的資料量通常比源資料要少得多。而為了提升運算速度,我們會增加Reducer的數量,Hive本身也會做類似優化——Reducer數量等於源資料的量除以hive.exec.reducers.bytes.per.reducer所配置的量(預設1G)。Reducer數量的增加也即意味著結果檔案的增加,從而產生小檔案的問題。
解決小檔案的問題可以從兩個方向入手:
1. 輸入合併。即在Map前合併小檔案
2. 輸出合併。即在輸出結果的時候合併小檔案

配置Map輸入合併

-- 每個Map最大輸入大小,決定合併後的檔案數
set mapred.max.split.size=256000000;
-- 一個節點上split的至少的大小 ,決定了多個data node上的檔案是否需要合併
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; 

配置Hive結果合併

我們可以通過一些配置項來使Hive在執行結束後對結果檔案進行合併:

hive.merge.mapfiles 在map-only job後合併檔案,預設true
hive.merge.mapredfiles 在map-reduce job後合併檔案,預設false
hive.merge.size.per.task 合併後每個檔案的大小,預設256000000
hive.merge.smallfiles.avgsize 平均檔案大小,是決定是否執行合併操作的閾值,預設16000000

Hive在對結果檔案進行合併時會執行一個額外的map-only指令碼,mapper的數量是檔案總大小除以size.per.task引數所得的值,觸發合併的條件是:
根據查詢型別不同,相應的mapfiles/mapredfiles引數需要開啟;
結果檔案的平均大小需要大於avgsize引數的值。
示例:

-- map-red job,5個reducer,產生5個60K的檔案。
create table dw_stage.zj_small as
select paid, count (*)
from dw_db.dw_soj_imp_dtl
where log_dt = '2014-04-14'
group by paid;
-- 執行額外的map-only job,一個mapper,產生一個300K的檔案。
set hive.merge.mapredfiles= true;
create table dw_stage.zj_small as
select paid, count (*)
from dw_db.dw_soj_imp_dtl
where log_dt = '2014-04-14'
group by paid;
-- map-only job,45個mapper,產生45個25M左右的檔案。
create table dw_stage.zj_small as
select *
from dw_db.dw_soj_imp_dtl
where log_dt = '2014-04-14'
and paid like '%baidu%' ;
-- 執行額外的map-only job,4個mapper,產生4個250M左右的檔案。
set hive.merge.smallfiles.avgsize=100000000;
create table dw_stage.zj_small as
select *
from dw_db.dw_soj_imp_dtl
where log_dt = '2014-04-14'
and paid like '%baidu%' ;

壓縮檔案的處理
對於輸出結果為壓縮檔案形式儲存的情況,要解決小檔案問題,如果在Map輸入前合併,對輸出的檔案儲存格式並沒有限制。但是如果使用輸出合併,則必須配合SequenceFile來儲存,否則無法進行合併,以下是示例:

set mapred.output.compression. type=BLOCK;
set hive.exec.compress.output= true;
set mapred.output.compression.codec=org.apache.hadoop.io.compress.LzoCodec;
set hive.merge.smallfiles.avgsize=100000000;
drop table if exists dw_stage.zj_small;
create table dw_stage.zj_small
STORED AS SEQUENCEFILE
as select *
from dw_db.dw_soj_imp_dtl
where log_dt = '2014-04-14'
and paid like '%baidu%' ;

使用HAR歸檔檔案

Hadoop的歸檔檔案格式也是解決小檔案問題的方式之一。而且Hive提供了原生支援:

set hive.archive.enabled= true;
set hive.archive.har.parentdir.settable= true;
set har.partfile.size=1099511627776;
ALTER TABLE srcpart ARCHIVE PARTITION(ds= '2008-04-08', hr= '12' );
ALTER TABLE srcpart UNARCHIVE PARTITION(ds= '2008-04-08', hr= '12' );

如果使用的不是分割槽表,則可建立成外部表,並使用har://協議來指定路徑。

相關文章