一、小文件產生的原因
當文件的大小遠遠小於HDFS block塊的大小(hadoop2:128m)就可以當作是一個小文件;具體產生的原因包括一下:
1)實時計算時,如果窗口開的小,在hdfs上會產生很多小文件
2)離線計算,批處理時,在spark或者mr時,沒有設置好partition或者reduce的個數,會產生小文件
3)Flume采集數據時,沒有配置好具體的滾動策略,會產生小文件
4)數據源本身存在很多小文件
二、小文件的影響
1)元數據影響:namenode將文件系統的元數據存放在內存中,因此存儲的文件數目受限於 namenode的內存大小。HDFS中每個文件、目錄、數據塊 占用150Bytes。如果存放的文件數目過多的話會占用很大的內存甚至撐爆內存;
2)mr任務影響:在mapreduce中,對每個文件都會啟動一個map task,如果小文件太多,影響性能;
3)在hdfs的讀流程里,如果小文件越多,尋址花費的時間越多
三、如何處理小文件
- 在向hdfs寫入數據前合並,包括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
- 對hdfs上的已存在的小文件進行合並
1)Hadoop Archive小文件歸檔:
hadoop archive -archiveName event.har -p /app/event -r 3 sub_path sub_path2 /app/har
event.har :歸檔文件的名稱
/app/event:需要歸檔文件的父目錄
sub_path: 子目錄,可以是多個,中間用空格分隔
/app/har:存儲的目錄
在歸檔結束之后,需要手動刪除歸檔后的路徑:
hdfs dfs -rmr /app/event/sub_path
2)CombineFileInputFormat<K,V> 小文件合並
3)手動合並,先將hdfs上的文件copy到本地,本地合並后在上傳到hdfs
#!/bin/bash bizdate=$1 path=$2 for (( i = 0; i < 10; i++ )); do bizhour="0${i}" echo "${bizdate} ${bizhour}" hdfs dfs -test -e /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=access/ if [ $? -ne 1 ]; then echo "進行hdfs操作" hdfs dfs -cat /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=access/part-00* | hdfs dfs -copyFromLocal - /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=access/part-11 hdfs dfs -rmr /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=access/part-00* fi hdfs dfs -test -e /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=action/ if [ $? -ne 1 ]; then echo "進行hdfs操作" hdfs dfs -cat /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=action/part-00* | hdfs dfs -copyFromLocal - /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=action/part-11 hdfs dfs -rmr /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=action/part-00* fi done for (( i = 10; i < 24; i++ )); do bizhour=${i} echo "${bizdate} ${bizhour}" hdfs dfs -test -e /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=access/ if [ $? -ne 1 ]; then echo "進行hdfs操作" hdfs dfs -cat /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=access/part-00* | hdfs dfs -copyFromLocal - /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=access/part-11 hdfs dfs -rmr /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=access/part-00* fi hdfs dfs -test -e /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=action/ if [ $? -ne 1 ]; then echo "進行hdfs操作" hdfs dfs -cat /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=action/part-00* | hdfs dfs -copyFromLocal - /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=action/part-11 hdfs dfs -rmr /${path}/bizdate=${bizdate}/bizhour=${bizhour}/log_type=action/part-00* fi done