在HDFS中,任何block,文件或者目录在内存中均以对象的形式存储,每个对象约占150byte,小文件过多会极大占用namonode的内存空间,从而制约集群的扩展。那么怎样处理HDFS中的小文件,下面我们一起看看。
一、采用HAR归档
hadoop Archive(HAR),是一个高效地将小文件放入HDFS块中的文件存档工具,它能够将多个小文件打包成一个HAR文件,这样在减少namenode内存使用的同时,仍然允许对文件进行透明的访问。
案例实现:对目录/input下的所有小文件存档成/output/zoo.har。
Hadoop archive -archiveName zoo.har -p /input /output
查看结果:
当然,也可以指定HAR的大小,使用-Dhar.block.size参数指定即可。
注意:创建HAR的过程是在运行一个MR作业。HAR在对小文件进行存档后,原文件不会被删除,且创建之后不能改变,要增加或移除里面的文件,必须重新创建归档文件。文件名中也不能有空格存在,否则会报异常。可以将空格用其他符号替换(使用-Dhar.space.replacement.enable和-Dhar.space.replacement参数)。
二、采用CombineTextInputFormat
Hadoop框架默认的TextInputFormat切片机制是对任务按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个MapTask,这样如果有大量小文件,就会产生大量的MapTask,处理效率极其低下。
CombineTextInputFormat用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样多个小文件就可以交给一个MapTask处理。
可以通过以下参数设置虚拟存储切片最大值,虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304); // 4M
切片过程:判断虚拟存储的文件大小是否大于setMaxInputSplitSize值,大于等于则单独形成一个切片。如果不大于则跟下一个虚拟存储文件进行合并,共同形成一个切片。
测试案例:有4个小文件大小分别为1.7M、5.1M、3.4M以及6.8M这四个小文件,则最终会形成3个切片,大小分别为:(1.7 2.55)M,(2.55 3.4)M,(3.4 3.4)M。
案例实现:
实现WordcountMapper 。
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
Text k = new Text();
IntWritable v = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行
String line = value.toString();
// 2 切割
String[] words = line.split(" ");
// 3 写出
for (String word : words) {
k.set(word);
context.write(k, v);
}
}
}
实现WordcountReducer 。
public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
int sum;
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
// 1 累加求和
sum = 0;
for (IntWritable count : values) {
sum = count.get();
}
// 2 写出
v.set(sum);
context.write(key,v);
}
}
实现Driver驱动类。
public class WordcountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取配置信息以及获取job对象
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2 关联本Driver程序的jar
job.setJarByClass(WordcountDriver.class);
// 3 关联Mapper和Reducer的jar
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
// 4 设置Mapper输出的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 如果不设置InputFormat,默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置4m
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);
// 6 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
查看结果:number of splits:3
最终会形成3个切片,大小分别为:(1.7 2.55)M,(2.55 3.4)M,(3.4 3.4)M。
三、开启JVM重用
一个MapTask运行会启动一个JVM,MapTask完成计算,JVM会销毁。开启JVM重用,让一个JVM可以运行多个MapTask程序。
小文件场景开启JVM重用,如果没有小文件,不要开启JVM重用,因为会一直占用Task卡槽,直到任务完成才释放。
JVM重用可以使得JVM实例在同一个Job中重新使用N次,N的值可以在Hadoop的mapred-site.xml文件中进行配置。通常在10-20之间。
<property>
<name>mapreduce.job.jvm.numtasks</name>
<value>10</value>
<description>How many tasks to run per jvm,if set to -1 ,there is no limit</description>
</property>
四、总结
本文介绍了Hadoop解决小文件问题的方案,包括Hadoop Archive、CombineTextInputFormat和开启JVM重用,同时介绍了部分参数的使用,希望能帮助有需要的人。