MapReduce是Hadoop的核心功能之一,我们首先需要弄明白MapReduce到底是个啥,是干啥子用滴才行。
什么是MapReduce
MapReduce是一种可用于数据处理的编程模型,我们现在设想一个场景,你接到一个任务,任务是:挖掘分析我国气象中心近年来的数据日志,该数据日志大小有3T
,让你分析计算出每一年的最高气温,如果你现在只有一台计算机,如何处理呢?我想你应该会读取这些数据,并且将读取到的数据与目前的最大气温值进行比较。比较完所有的数据之后就可以得出最高气温了。不过以我们的经验都知道要处理这么多数据肯定是非常耗时的。
如果我现在给你三台机器,你会如何处理呢?看到下图你应该想到了:最好的处理方式是将这些数据切分成三块,然后分别计算处理这些数据(Map),处理完毕之后发送到一台机器上进行合并(merge),再计算合并之后的数据,归纳(reduce)并输出。
这就是一个比较完整的MapReduce的过程了。
如何使用MapReduce进行运算
我们通过一个示例,来体验Map/Reduce
的使用。
我们从一个问题入手:目前我们想统计两个文本文件中,每个单词出现的次数。
首先我们在当前目录下创建两个文件:
创建file01
输入内容:
Hello World Bye World
创建file02
输入内容:
Hello Hadoop Goodbye Hadoop
将文件上传到HDFS的/usr/input/
目录下:
不要忘了启动DFS:
start-dfs.sh
然后创建文件夹并上传:
编写,文件WordCount.java
,添加如下内容:
public class WordCount {
public static class TokenizerMapper
extends Mapper{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(LongWritable key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer
extends Reducer {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = new Job(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);
String inputfile = "/usr/input";
String outputFile = "/usr/output";
FileInputFormat.addInputPath(job, new Path(inputfile));
FileOutputFormat.setOutputPath(job, new Path(outputFile));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
运行代码,可以看到/usr/output
目录下已经生成了文件。
我们来查看part--r-00000
文件的内容:
可以看到统计的数据已经生成在文件中了。
如果你还想要运行一次,那么你需要删除输出路径的文件夹和文件。
代码解释
示例中,Map/Reduce
程序总共分为三块即:Map,Recude,Job
,Map
负责处理输入文件的内容,
TokenizerMapper
的map
方法,它通过StringTokenizer
以空格为分隔符将一行切分为若干tokens
,之后,输出<
形式的键值对。
对于示例中的第一个输入,map
输出是:
< Hello, 1>
< World, 1>
< Bye, 1>
< World, 1>
第二个输入,map
输出是:
< Hello, 1>
< Hadoop, 1>
< Goodbye, 1>
< Hadoop, 1>
WordCount
还指定了一个combiner
。因此,每次map
运行之后,会对输出按照key
进行排序,然后把输出传递给本地的combiner
(按照作业的配置与Reducer
一样),进行本地聚合。
第一个map
的输出是:
< Bye, 1>
< Hello, 1>
< World, 2>
第二个map
的输出是:
< Goodbye, 1>
< Hadoop, 2>
< Hello, 1>
reduce
收到的数据是这样的:
< Bye , [1]>
< GoodBye , [1]>
< Hadoop , [1,1]>
< Hello , [1,1]>
< World , [1,1]>
Reducer
中的reduce
方法 仅是将每个key
(本例中就是单词)出现的次数求和。
因此这个作业的输出就是:
< Bye, 1>
< Goodbye, 1>
< Hadoop, 2>
< Hello, 2>
< World, 2>
学以致用
输入文件的数据格式如下:
张三 12
李四 13
张三 89
李四 92
...
使用MapReduce
计算班级每个学生的最好成绩,输入文件路径为/user/test/input
,请将计算后的结果输出到/user/test/output/
目录下。
通过上面的练习你应该了解了MapReduce大致的使用方式,现在我们来了解一下Mapper
类,Reducer
类和Job
类。
map类
首先我们来看看Mapper
对象:
在编写MapReduce
程序时,要编写一个类继承Mapper
类,这个Mapper
类是一个泛型类型,它有四个形参类型,分别制定了map()
函数的输入键,输入值,和输出键,输出值的类型。就第一关的例子来说,输入键是一个长整型,输入值是一行文本,输出键是单词,输出值是单词出现的次数。
Hadoop提供了一套可优化网络序列化传输的基本类型,而不是直接使用Java内嵌的类型。这些类型都在org.apache.hadoop.io
包中,这里使用LongWritable
(相当于Java中的Long
类型),Text
类型(相当于Java中的String
类型)和IntWritable
(相当于Integer
类型)。
map()
函数的输入是一个键和一个值,我们一般首先将包含有一行输入的text
值转换成Java的String
类型,然后再使用对字符串操作的类或者其他方法进行操作即可。
Reducer类
同样Reducer
也有四个参数类型用于指定输入和输出类型,reduce()
函数的输入类型必须匹配map
函数的输出类型,即Text
类型和IntWritable
类型,在这种情况下,reduce
函数的输出类型也必须是Text
和IntWritable
类型,即分别输出单词和次数。
Job类
一般我们用Job
对象来运行MapReduce
作业,Job
对象用于指定作业执行规范,我们可以用它来控制整个作业的运行,我们在Hadoop集群上运行这个作业时,要把代码打包成一个JAR文件(Hadoop在集群上发布的这个文件),不用明确指定JAR文件的名称,在Job
对象的setJarByClass()
函数中传入一个类即可,Hadoop利用这个类来查找包含他的JAR文件。addInputPath()
函数和setOutputPath()
函数用来指定作业的输入路径和输出路径。值的注意的是,输出路径在执行程序之前不能存在,否则Hadoop会拒绝执行你的代码。
最后我们使用waitForCompletion()
方法提交代码并等待执行,该方法唯一的参数是一个布尔类型的值,当该值为true
时,作业会把执行过程打印到控制台,该方法也会返回一个布尔值,表示执行的成败。
巩固练习
接下来我们通过一个练习来巩固学习到的MapReduce
知识吧。
对于两个输入文件,即文件file1
和文件file2
,请编写MapReduce
程序,对两个文件进行合并,并剔除其中重复的内容,得到一个新的输出文件file3
。
为了完成文件合并去重的任务,你编写的程序要能将含有重复内容的不同文件合并到一个没有重复的整合文件,规则如下:
- 第一列按学号排列;
- 学号相同,按
x,y,z
排列。 - 输入文件路径为:
/user/tmp/input/
- 输出路径为:
/user/tmp/output/
输入文件file1
的样例如下:
20150101 x
20150102 y
20150103 x
20150104 y
20150105 z
20150106 x
输入文件file2
的样例如下:
20150101 y
20150102 y
20150103 x
20150104 z
20150105 y
根据输入文件file1
和file2
合并得到的输出文件file3
的样例如下:
20150101 x
20150101 y
20150102 y
20150103 x
20150104 y
20150104 z
20150105 y
20150105 z
20150106 x
做个练习题巩固一下吧。