1. 小文件的產生原因
定義: 當一個文件的大小小於 HDFS 的塊大小(默認128MB)就認定為小文件,否則就是大文件
-
批處理,離線計算, 會有小文件的產生;
-
數據處理時,把數據源搬遷到 HDFS,如果數據源本身就是有很多小文件;
-
MapReduce作業 和 Spark作業時,沒有設置好 Reduce Task個數,或者spark最后一層Task的數量。
2. 小文件的危害
-
HDFS不適合大量小文件的存儲,因namenode將文件系統的元數據存放在內存中,因此存儲的文件數目受限於 namenode的內存大小。HDFS中每個文件、目錄、數據塊 占用150Bytes。如果存放的文件數目過多的話會占用很大的內存甚至撐爆內存;
-
HDFS適用於高吞吐量,而不適合低時間延遲的訪問。如果同時存入大量的小文件會花費很長的時間;
-
流式讀取的方式,不適合多用戶寫入,以及任意位置寫入。如果訪問小文件,則必須從一個DataNode跳轉到另外一個DataNode,這樣大大降低了讀取性能;
-
小文件過多,盡管hadoop集群配置了HA ,一旦原先NameNode掛掉, 備用的NameNode也會因小文件多,導致Block多,NameNode啟動是把FsImage磁盤數據和Edits操作日志加載到內存的過程,Block越多,讀取耗時也越長,啟動就會很慢;
3. 解決方案
處理指導原則:
-
在寫入HDFS前進行處理 (優先考慮,此處只考慮了mr產生的,暫不考慮基於tez,spark等其他寫入hdfs的途徑)
-
- 輸入合並,在Map前合並小文件;
-
- 輸出合並,在Reduce寫入磁盤前合並小文件;
-
-
在寫入hdfs后進行處理
-
- HDFS中的小文件合並
-
具體操作方法:
對於小文件合並,一般的做法是編寫腳本、程序完成合並。
3.1 通過參數調節,設置map/reduce的數量
#設置map輸入合並小文件的相關參數:
//每個Map最大輸入大小(這個值決定了合並后文件的數量,調大可以減小Mapper數),默認128M
set mapred.max.split.size=1024*1024*512;
//一個節點上split的至少的大小(小於這個值會進行合並,這個值決定了多個DataNode上的文件是否需要合並)
set mapred.min.split.size.per.node=1024*1024*512;
//一個交換機下split的至少的大小(這個值決定了多個交換機上的文件是否需要合並)
set mapred.min.split.size.per.rack=100000000;
//設置hive輸入端進行小文件合並
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
# 減少reduce的數量
-- 除非知道數據量的分布,一般不直接設置
set mapred.reduce.tasks=10;
-- 每個reduce處理的數據量,實際上可能每個reduce 處理的數據量會超過它,默認1G 增加該參數,可以減小reduce數量
set hive.exec.reducers.bytes.per.reducer=1073741824
# 設置map輸出和reduce輸出進行合並的相關參數:
//設置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
//ps:理論上設置了輸出端小文件合並,會減少小文件的數量,可惜實際中即使設置了這些參數,對tdw分區表並不起作用
// hadoop v2.x 后一些參數名發生了變化
mapred.min.split.size => mapreduce.input.fileinputformat.split.minsize。
mapred.max.split.size => mapreduce.input.fileinputformat.split.maxsize。
mapred.min.split.size.per.rack => mapreduce.input.fileinputformat.split.minsize.per.rack。
mapred.min.split.size.per.node => mapreduce.input.fileinputformat.split.minsize.per.node。
dfs.block.size => dfs.blocksize
mapred.map.tasks => mapreduce.job.maps
3.2 hadoop 自帶的小文件處理方法
1) Hadoop Archive 小文件歸檔
// 創建歸檔文件 把 input目錄下所有文件 歸檔到 output/input.har, 原先input下的文件依然存在,需要手動刪除才能釋放原先在namenode中元數據占用的內存
hadoop archive -archiveName input.har -p /user/kuncai/input /user/kuncai/output
// 查看歸檔文件
hadoop fs -ls har:///user/kuncai/output3/input.har
// 解壓歸檔文件
hadoop fs -cp har:///user/kuncai/output3/input.har/* /user/kuncai/
ps: 文件是許多記錄組成的,那么可以通過調用 HDFS 的
sync()
方法(和append
方法結合使用),每隔一定時間生成一個大文件。另外:hive中的數據,也可以通過sort by 和 distrubute by 重建表,建表時減少reduce的數量,從而減少小文件;
2) SequenceFile 合並小文件;
原理: 使用文件名作為 key,文件內容作為 value ,把多個文件數據格式轉換后統一輸出;
通過自定義MR中的 Mapper,Driver,Reducer方法;
具體代碼實現可參考此鏈接: https://examples.javacodegeeks.com/enterprise-java/apache-hadoop/hadoop-sequence-file-example/
3) CombineFileInputFormat<K,V> 小文件合並
核心思想:
根據一定的規則,將 HDFS 上多個小文件合並到一個 InputSplit 中,然后會啟用一個 Map 來處理這里面的文件,以此減少MR整體作業的運行時間
具體代碼實現可以參考此鏈接: https://blog.csdn.net/u011007180/article/details/52333387
4) HBase
如果你產生很多小文件,根據訪問模式的不同,應該進行不同類型的存儲。HBase 將數據存儲在 Map Files(帶索引的 SequenceFile)中,如果你需要隨機訪問來執行 MapReduce 流式分析,這是一個不錯的選擇。如果延遲是一個問題,那么還有很多其他選擇 - 參見Richard Jones對鍵值存儲的調查。
Other參考鏈接:
Hadoop 大量小文件問題的優化 https://cloud.tencent.com/developer/article/1482598
Hive-小文件優化實戰 https://www.jianshu.com/p/8f0ce9eb0d0b