MapReduce 框架默认的 TextInputFormat 切片机制是对任务按文件规划切片,如果有大量小文件,就会产生大量的 MapTask,处理小文件效率非常低。
CombineTextInputFormat:用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个 MapTask 处理。
CombineTextInputFormat 切片机制过程包括:虚拟存储过程和切片过程二部分 假设 setMaxInputSplitSize 值为 4M,有如下四个文件 a.txt 1.7M b.txt 5.1M c.txt 3.4M d.txt 6.8M (1)虚拟存储过程 (1.1)将输入目录下所有文件大小,依次和设置的 setMaxInputSplitSize 值比较,如果不大于设置的最大值,逻辑上划分一个块。
(1.2)如果输入文件大于设置的最大值且大于两倍,那么以最大值切割一块,当剩余数据大小超过设置的最大值且不大于最大值2倍,此时将文件均分成2个虚拟存储块(防止出现太小切片)。 1.7M < 4M 划分一块 5.1M > 4M 但是小于 2*4M 划分二块:块1=2.55M,块2=2.55M 3.4M < 4M 划分一块 6.8M > 4M 但是小于 2*4M 划分二块:块1=3.4M,块2=3.4M 最终存储的文件: 1.7M 2.55M,2.55M 3.4M 3.4M,3.4M (2)切片过程 (2.1)判断虚拟存储的文件大小是否大于 setlMaxIputSplitSize 值,大于等于则单独形成一个切片。 (2.2)如果不大于则跟下一个虚拟存储文件进行合并,共同形成一个切片。 最终会形成3个切片: (1.7+2.55)M,(2.55+3.4)M,(34+3.4)M
测试读取数据的方式
控制台日志
可以看到读取方式与 TextInputFormat 一样,k 为偏移量,v 为一行的值,按行读取
以 WordCount 为例进行测试,测试切片数
测试数据
测试代码
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.CombineTextInputFormat; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.log4j.BasicConfigurator; import java.io.IOException; import java.util.StringTokenizer; public class WordCount { static { try { // 设置 HADOOP_HOME 环境变量 System.setProperty("hadoop.home.dir", "D:/DevelopTools/hadoop-2.9.2/"); // 日志初始化 BasicConfigurator.configure(); // 加载库文件 System.load("D:/DevelopTools/hadoop-2.9.2/bin/hadoop.dll"); } catch (UnsatisfiedLinkError e) { System.err.println("Native code library failed to load. " + e); System.exit(1); } } public static void main(String[] args) throws Exception { args = new String[]{"D:\tmp\input", "D:\tmp\456"}; Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "word count"); job.setJarByClass(WordCount.class); job.setMapperClass(TokenizerMapper.class); job.setCombinerClass(IntSumReducer.class); job.setReducerClass(IntSumReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // 设置 InputFormat,默认为 TextInputFormat.class job.setInputFormatClass(CombineTextInputFormat.class); // 设置最大值即可 128M CombineTextInputFormat.setMaxInputSplitSize(job, 1024 * 1024 * 128); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> { private final static IntWritable one = new IntWritable(1); private Text word = new Text(); @Override public void map(Object key, Text value, Context context) throws IOException, InterruptedException { // 查看 k-v // System.out.println(key + " " + value); StringTokenizer itr = new StringTokenizer(value.toString()); while (itr.hasMoreTokens()) { word.set(itr.nextToken()); context.write(word, one); } } } public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); @Override public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } } }
由于所有文件加起来大小都没有 128M,所以切片数为 1