Hadoop WordCount详解(2.7.1版本)

本文代码是Hadoop 2.7.1版本中example的,有些API可能跟之前的版本不太一样

这里主要讲代码中使用的各个API,关于Word Count的基本思想仅略讲

Word Count基本思想

Hadoop基本思想是分而治之。对于一个非常大的文档做word count,单机处理起来会比较慢。所以我们部署多个机器,每个机器上开启若干个线程来处理。

基本的Word Count

首先,将这个大文档拆分(比如按行拆分),然后将这若干个部分分发给各个机器上的若干个mapper线程。每个mapper接收一段文档(比如一行),然后对这段文档进行tokenizing,即拆成若干个word。每看见一个word,就输出一个key-value对(word,1),代表这个word出现了1次。

当所有的mapper处理完之后,Hadoop的Master Node启动一系列reducer线程。每个Reducer接收一个key(这里就是一个word)的所有key-value对。然后通过以这个word为key的所有key-value对,来统计这个word的出现次数,然后输出(word,count)。表示这个word在文档中出现了几次。

加入combiner

不过,上述方法中,mapper会产生大量的key-value对,会极大占用网络传输资源。所以,我们可以在mapper后面加一个combiner。其执行跟reducer相同的操作,也是接收关于一个key的所有key-value对,然后将word count进行sum。

这样,将combiner的输出传递给reducer就会极大地减少传输的key-value对的数量。

combiner跟reducer的不同之处在于:

  • combiner是在mapper工作时就被调用的。即如果有10个mapper,它可能在3个mapper工作完毕后就被调用一次,用以合并者3个mapper的结果。然后又有5个mapper完工后再被调用一次。
  • Hadoop不保证combiner一定被调用,也不保证combiner会被调用几次。
  • combiner在一部分mapper运行完后即可被调用
  • 所以,combiner可以看做是一个mini reducer

代码解析

为了方便阅读,这里就直接以注释的形式来解释了

Hadoop 2.7.1版本的代码如下:

import java.io.IOException;
import java.util.StringTokenizer;

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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

public class WordCount {

  // Mapper<输入key, 输入value, 输出key, 输出value>
  // Hadoop默认使用TextInputFormat(本例也是)
  // TextInputFormat按行对文件进行分割,并传给Mapper
  // 输入key表示当前行的第一个byte是整个文件的第几个byte,实际是LongWritable类型
  // 这里用Object是为了通用性考虑,完全可以换成LongWritable
  public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{

    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();

    public void map(Object key, Text value, Context context
                    ) throws IOException, InterruptedException {
      // 对文本分词(tokenizing)
      StringTokenizer itr = new StringTokenizer(value.toString());
      while (itr.hasMoreTokens()) {
        word.set(itr.nextToken());
        // context可以简单看作是mapper运行环境的封装(存储输出等)
        context.write(word, one);
      }
    }
  }

  // Reducer<输入key, 输入value, 输出key, 输出value>
  public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
    private IntWritable result = new IntWritable();

    public void reduce(Text key, Iterable<IntWritable> 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 {
    // 用来生成一些MapReduce的默认参数
    Configuration conf = new Configuration();
    // GenericOptionsParser用来parse输入参数中的Hadoop参数,并作相应处理,比如设置namenode等
    // 后面的getRemainingArgs返回Hadoop参数以外的部分,留给具体的应用处理
    String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
    if (otherArgs.length < 2) {
      System.err.println("Usage: wordcount <in> [<in>...] <out>");
      System.exit(2);
    }
    // 第二个参数是job name,直接用Job构造函数初始化在2.7.1中是deprecated
    Job job = Job.getInstance(conf, "word count");
    // 找到包含WordCount这个class的jar包
    // 从而告诉各个node要在哪找这个job需要的mapper跟reducer函数
    job.setJarByClass(WordCount.class);
    job.setMapperClass(TokenizerMapper.class);
    // combiner的作用上文有讲
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    // 设置输出key-value对的类别
    job.setOutputKeyClass(Text.class);
    // IntWritable的含义是,Hadoop实现了更加符合Hadoop机制的对int序列化的方法
    job.setOutputValueClass(IntWritable.class);
    for (int i = 0; i < otherArgs.length - 1; ++i) {
      // InputFormat包含对input的描述和处理方法
      // FileInputFormat是所有基于文件的InputFormat的基类
      // InputFormat的类一般会提供一些对job的input进行操作的函数
      // 这里用FileInputFormat是为了保证通用性
      FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
    }
    FileOutputFormat.setOutputPath(job,
      new Path(otherArgs[otherArgs.length - 1]));
    // job.waitForCompletion提交任务,输入参数指定是否等待任务完成
    // 如果等待任务完成,当正常完成时返回true,反之false
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}



在eclipse下的运行方法见我上一篇博客

你可能感兴趣的:(hadoop)