统计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); } }