上一篇文章介绍了Hadoop的单机配置以及一个简单的MapReduce示例,今天看看MapReduce处理数据的流程是怎样的。建议阅读本文前,最好能看一下上一篇文章的代码。
上图以上一篇文章的MapReduce示例为例,展示了单机配置下MapReduce的处理流程,由于单机情况下更容易理解处理流程,所以这篇文章以单机处理为例,实际上,分布式配置时,也是这样的流程,只是在每个环节的数据形式有所不同,后面的文章会进行介绍。先来看一下上面的流程。
1.input
该阶段很简单,就是将输入文件中的内容按行分割为key和value的形式。看一下上篇文章中的map方法的参数
protected void map(LongWritable key, Text value, Context context)这里的key和value,就对应input阶段生成的key和value。
2.Map
Map阶段所做的工作就是我们map方法所做的事情,这是由我们自己定义的,主要对input实现细分和过滤,保留我们需要的数据,以key和value的形式,通过context输出。之前例子中,我们在map方法中取出了每一个单词的首字母,以首字母作为key,数值1作为value进行输出。
3.Shuffle
注意上图中Map输出中所示的B,有两行,也就是说,Map阶段的输出,会有key相同的记录。而且,输出是无序的。在Shuffle阶段,会对map的输出按照key进行合并和排序,然后作为reduce的输入使用。如果使用自定义的类对象作为key,该类可以实现WritableComparable接口,定义自己的排序方式。
4.Reduce
Reduce的过程完成reduce方法所做的事情。我们的示例中将所属key下的1进行了叠加,从而计算出一个字母作为首字母出现的次数。最后结果以key和value的方式输出。
以上就是MapReduce的处理过程,最后说明一下,我们在派生Mapper类和Reducer类时,都分别传入了四个类型参数,他们分别对应Map和Reduce阶段的输入key和value类型以及输出key和value类型。对于基本类型,Hadoop提供了可序列化类,如示例中的LongWritable(对应Long),Text(对应String)等,最好使用这些类,以方便Hadoop在分布式情况下对数据进行处理。
了解了处理流程,这里再给出两个简单示例,方便大家理解。先看第一个示例,我们将英文文章中出现的单词进行排序,使用的输入仍然是上一篇文章中的text。代码如下:
package com.yjp.mapreduce; import java.io.IOException; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.LongWritable; 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.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class WordSort { // 执行Map private static class WordSortMapper extends Mapper<LongWritable, Text, Text, Text> { @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); StringTokenizer token = new StringTokenizer(line); while (token.hasMoreTokens()) { String word = token.nextToken(); Pattern p = Pattern.compile("[A-Za-z]+"); Matcher m = p.matcher(word); if (m.find()) { context.write(new Text(m.group(0)), new Text()); } } } } // 执行Reduce private static class WordSortReducer extends Reducer<Text, Text, Text, Text> { @Override protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException { context.write(key, new Text()); } } public static void main(String[] args) throws Exception { if (args.length != 2) { System.err.println("Usage: WordSort <inout path> <output path>"); System.exit(-1); } // 设置类信息,方便hadoop从JAR文件中找到 Job job = Job.getInstance(); job.setJarByClass(WordSort.class); job.setJobName("Word Sort"); // 添加输入输出路径 FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); // 设置执行Map和Reduce的类 job.setMapperClass(WordSortMapper.class); job.setReducerClass(WordSortReducer.class); //设置输出数据类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); System.exit(job.waitForCompletion(true) ? 0 : 1); } }这个例子很简单,上面的流程我们分析过,执行完Shuffle实际上已经完成了归并和排序,所以,我们这个示例,Reduce过程很简单,直接将传递进来的输入进行输出即可。
第二个示例,我们统计一段文字中出现的字母及其数目。了解了流程,这就简单多了,我们只要基于上一篇文章的示例,修改它的map方法即可
@Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); StringTokenizer token = new StringTokenizer(line); while (token.hasMoreTokens()) { String word = token.nextToken(); Pattern p = Pattern.compile("[A-Za-z]+"); Matcher m = p.matcher(word); if (m.find()) { word = m.group(0); for (int i = 0; i < word.length(); i++) { context.write(new Text(word.charAt(i) + ""), one); } } } }代码已上传直github.