一、小文件产生的原因
当文件的大小远远小于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
