直到目前,我们看到的所有MapReduce作业都输出一组文件。但是,在一些场合下,经常要求我们输出多组文件或者把一个数据集分为多个数据集更为方便;比如将一个log里面属于不同业务线的日志分开来输出,并且交给相关的业务线。
用过旧API的人应该知道,旧API中有org.apache.hadoop.mapred.lib.MultipleOutputFormat和org.apache.hadoop.mapred.lib.MultipleOutputs两个重要的类,但是由于旧版本的MultipleOutputFormat是基于行的划分而MultipleOutputs是基于列的划分。所以在新的API中就剩下了MultipleOutputs(mapreduce包中)类,这个类合并了旧API中的MultipleOutputFormat和MultipleOutputs的功能,同时新版的类库中已经不存在MultipleOutputFormat类了,因为MultipleOutputs都有它的功能了,还要它干嘛。
下面我们通过一个例子来深入体会新版API中的MultipleOutputs的功能。
测试数据:
3070818,1963,1096,,"US","IN",,1,,441,6,69,,4,,0.625,,,,,,, 3070819,1963,1096,,"US","TN",,4,,12,6,63,,0,,,,,,,,, 3070820,1963,1096,,"GB","",,2,,12,6,63,,0,,,,,,,,, 3070821,1963,1096,,"US","IL",,2,,15,6,69,,1,,0,,,,,,, 3070822,1963,1096,,"US","NY",,2,,401,1,12,,4,,0.375,,,,,,, 3070823,1963,1096,,"US","MI",,1,,401,1,12,,8,,0.6563,,,,,,, 3070824,1963,1096,,"US","IL",,1,,401,1,12,,5,,0.48,,,,,,, 3070825,1963,1096,,"US","IL",,1,,401,1,12,,7,,0.6531,,,,,,, 3070826,1963,1096,,"US","IA",,1,,401,1,12,,1,,0,,,,,,, 3070827,1963,1096,,"US","CA",,4,,401,1,12,,2,,0.5,,,,,,, 3070828,1963,1096,,"US","CT",,2,,16,5,59,,4,,0.625,,,,,,, 3070829,1963,1096,,"FR","",,3,,16,5,59,,5,,0.48,,,,,,, 3070830,1963,1096,,"US","NH",,2,,16,5,59,,0,,,,,,,,, 3070831,1963,1096,,"US","CT",,2,,16,5,59,,0,,,,,,,,,
实现代码:
public class MultipleOutputsTest { // 定义输入路径 private static final String INPUT_PATH = "hdfs://liaozhongmin:9000/multipleOutput/multipleOutput_data"; // 定义输出路径 private static final String OUT_PATH = "hdfs://liaozhongmin:9000/out"; public static void main(String[] args) { try { // 创建配置信息 Configuration conf = new Configuration(); // 创建文件系统 FileSystem fileSystem = FileSystem.get(new URI(OUT_PATH), conf); // 如果输出目录存在,我们就删除 if (fileSystem.exists(new Path(OUT_PATH))) { fileSystem.delete(new Path(OUT_PATH), true); } // 创建任务 Job job = new Job(conf, MultipleOutputsTest.class.getName()); //1.1 设置输入目录和设置输入数据格式化的类 FileInputFormat.setInputPaths(job, INPUT_PATH); job.setInputFormatClass(TextInputFormat.class); //1.2 设置自定义Mapper类和设置map函数输出数据的key和value的类型 job.setMapperClass(MultipleOutputsMapper.class); job.setMapOutputKeyClass(NullWritable.class); job.setMapOutputValueClass(Text.class); //1.3 设置分区和reduce数量(reduce的数量,和分区的数量对应) job.setPartitionerClass(HashPartitioner.class); job.setNumReduceTasks(0); //1.4 排序 //1.5 归约 //2.1 Shuffle把数据从Map端拷贝到Reduce端。 //2.2 指定Reducer类和输出key和value的类型 //job.setReducerClass(MyReducer.class); job.setOutputKeyClass(NullWritable.class); job.setOutputValueClass(Text.class); //2.3 指定输出的路径和设置输出的格式化类 FileOutputFormat.setOutputPath(job, new Path(OUT_PATH)); job.setOutputFormatClass(TextOutputFormat.class); // 提交作业 退出 System.exit(job.waitForCompletion(true) ? 0 : 1); } catch (Exception e) { e.printStackTrace(); } } public static class MultipleOutputsMapper extends Mapper<LongWritable, Text, NullWritable, Text>{ //定义MultipleOutputs private MultipleOutputs<NullWritable,Text> multipleOutputs = null; @Override protected void setup(Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException { //初始化MultipleOutputs对象 multipleOutputs = new MultipleOutputs<NullWritable,Text>(context); } protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException { //把结果直接写出去(注意这里不是context而是MultipleOutps的对象) multipleOutputs.write(NullWritable.get(), value, generateFileName(value)); } /** * 自定义一个产生文件名的方法 * @param value * @return */ private String generateFileName(Text value){ //对字符串进行切分 String[] splits = value.toString().split(","); //截取国家的字符串 String country = splits[4].substring(1, 3); //返回一个包含国家的文件名 return country + "/"; } /** * 用完MultipleOutputs之后一定要关闭 */ @Override protected void cleanup(Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException { multipleOutputs.close(); } } }程序运行结果:
如上图所示,我们的结果按国家写在了不同的目录下,但是有个奇怪的问题就是,在输出结果中还有两个以part开头的文件,里面什么内容也没有,这是怎么回事呢?原因是他们都是程序的默认输出文件,而我们自定义的输出格式不能以part开头,那么我们如何去掉这两个不太和谐的文件呢?其实很简单,在main()函数中加入以下一行代码:
LazyOutputFormat.setOutputFormatClass(job, TextOutputFormat.class);如果加入了上面一行代码,请同时注释掉你代码中下面一行代码(如果有)
job.setOutputFormatClass(TextOutputFormat.class);
文章来自:过往记忆:http://www.iteblog.com/archives/848
另外附上两篇文章:
http://my.oschina.net/leejun2005/blog/94706
http://tydldd.iteye.com/blog/2053867