统计hdfs某个文件中重复单词的数目(WordCount),每个单词用空格分隔,统计每个单词的出现频率,说明MapReduce框架的过程。
进行map过程之前,进行Pre-Map过程。框架帮我们把文件切分,NameNode把文件切分成block,每个block最大为128M;文件小于128M时,文件多大形成的block就是多大;一个block不能跨多个文件。
分隔好后,针对每个文件内部,再根据换行符\n分割文件内容,形成key-value对,其中key是这一行首相对文件开头的偏移量,如图中<0,"c b a">,<5,"d c b">,其中“d c b”是第二行,相对于第一行第一个字,d是第五个字符。
这样的key-value对传入map,有几个文件就形成几个map,map内部分成分区(partition)、排序(sort)、有时还有Combine过程。当Pre-Map过程形成的key-value进入map后,map方法逐个处理value,这就开始需要我们自己写一些代码来定义map方法,根据需求,我们需要按空格把每个词分开,将其作为新的key,给每一个词分配相同的value,我们现在指定value为1,这样就形成了新的key-value对,如<"a",1>、<"b",1>、<"c",1>、<"d",1>、<“e”,1>。接下来进入分区过程,分区的依据是用key的哈希值模reduce的个数,当不指定reduce个数是,默认它为1。分区结束后,key-value进入这些分区,并在分区中进行按asc码的排序(sort)。排序结束后,如果我们设定了combine过程,将在同一个分区内进行局部运算(其实是map端的reduce计算),如图中对partition1中相同的部分做了合并形成了<"b",2>,<"c",2>。
接下来进入reduce,同一分区的放到一起,当文件特别大时,用partition0举例,如果某一个partition0有1G数据,另外一个partition0有2G数据,多个partition0的数据合起来有几十G,很明显内存是不够用的,这时候数据fetch到reduce中形成文件时,需要先把一部分数据在内存处理后,经过聚合(merge),再放到磁盘中形成文件,同时进行排序(sort),让key相同的都排列在一起。框架自动把key相同的value放到一起,此时形成了键组,如先前的<"a",1><"a",1><"b",1><"b",2>,形成了键组<"a",{1,1}>,<"b",{1,2}>,然后进入reduce方法,现在又需要自己定义reduce方法,reduce内的key都是不重复的,形成了以a为key,以数组{1,1}的value。把一个键组内的value叠加求和,就可以求出某一单词出现的次数。
注意:对于combine过程,它可以减轻io的负担,减少计算量,但是并不是所有的过程都可以进行combine(局部计算),比如计算平均数,表格的左连接和右连接。
package com.wh.mapper;
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class MyTokenizerMapper extends
Mapper<Object, Text, Text, IntWritable> {
// 暂存每个传过来的词频计数,均为1,省掉重复申请空间
private final static IntWritable one = new IntWritable(1);
// 暂存每个传过来的词的值,省掉重复申请空间
private Text word = new Text();
// 核心map方法的具体实现,逐个<key,value>对去处理
public void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
// 用每行的字符串值初始化StringTokenizer
StringTokenizer itr = new StringTokenizer(value.toString());
// 循环取得每个空白符分隔出来的每个元素
while (itr.hasMoreTokens()) {
// 将取得出的每个元素放到word Text对象中
word.set(itr.nextToken());
// 通过context对象,将map的输出逐个输出
context.write(word, one);
}
}
}
Reducer类编写
新类介绍
Reducer:是MapReduce计算框架中Reduce过程的封装
源码编写
package com.wh.reducer;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
//reduce类,实现reduce函数
public class IntSumReducer extends
Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
//核心reduce方法的具体实现,逐个<key,List(v1,v2)>去处理
public void reduce(Text key, Iterable<IntWritable> values,
Context context) throws IOException, InterruptedException {
//暂存每个key组中计算总和
int sum = 0;
//加强型for,依次获取迭代器中的每个元素值,即为一个一个的词频数值
for (IntWritable val : values) {
//将key组中的每个词频数值sum到一起
sum += val.get();
}
//将该key组sum完成的值放到result IntWritable中,使可以序列化输出
result.set(sum);
//将计算结果逐条输出
context.write(key, result);
}
}
Driver类编写
新类介绍
Configuration:与hdfs处的configuration一致,负责参数的加载和传递。
Job:是对一轮MapReduce任务的抽象,即一个MapReduce的执行全过程的管理类。
FileInputFormat:指定输入数据的工具类,用于指定任务的输入数据路径。
FileOutputFormat:指定输出数据的工具类,用于指定任务的输出数据路径。
源码编写
package com.wh.driver;
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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import com.tianliangedu.mapper.MyTokenizerMapper;
import com.tianliangedu.reducer.IntSumReducer;
public class WordCount {
// 启动mr的driver方法
public static void main(String[] args) throws Exception {
// 得到集群配置参数
Configuration conf = new Configuration();
// 设置到本次的job实例中
Job job = Job.getInstance(conf, "Mrs.Wu HO_o的WordCount");
// 指定本次执行的主类是WordCount
job.setJarByClass(WordCount.class);
// 指定map类
job.setMapperClass(MyTokenizerMapper.class);
// 指定combiner类,要么不指定,如果指定,一般与reducer类相同
job.setCombinerClass(IntSumReducer.class);
// 指定reducer类
job.setReducerClass(IntSumReducer.class);
// 指定job输出的key和value的类型,如果map和reduce输出类型不完全相同,需要重新设置map的output的key和value的class类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 指定输入数据的路径
FileInputFormat.addInputPath(job, new Path(args[0]));
// 指定输出路径,并要求该输出路径一定是不存在的
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 指定job执行模式,等待任务执行完成后,提交任务的客户端才会退出!
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}