例题详解MapReduce过程

1、概述

MapReduce程序主要可分为三部分,即:mapper、reducer、driver(即main函数提交作业部分),根据需求不同可以设置partitioner、combinner以及cleanup部分。后三部分作用分别描述如下:

  • mapper:实现从输入文件中以(key,value)的形式获取想要提取的数据;

  • reducer:实现将mapper的输出结果汇总、归并;

  • driver:负责作业设置、作业提交;

  • partitioner:实现将Map阶段的输出结果按指定的方法分到不同的分区中,为reduce阶段做准备。若没有自定义分区方法,则会启动默认的partitioner,它根据ReduceTask的数量将Map的结果进行分区;

  • combinner:在进入reduce之前对map的输出结果进行组合,例如:在执行Word Count程序时,map端输出结果有100万个(hello,1)的key/value对;若将其直接传到reduce端,需要传输100万次,当这个reduce在另一台服务器时需要消耗大量的网络带宽资源,运行效率不高;但是,若将这100万个(hello,1)组合成(hello,1000000)传到reduce端,则只需要传递1次,可以大大地提升效率;

  • cleanup:Mapper类与Reducer类中都包含了这个方法,该方法在MapReduce程序执行过程中只运行一次,实现Mapper/Reducer类的清尾工作。

例题详解MapReduce过程_第1张图片

2、任务描述

假设输入文本中有若干个单词,要求对文本中的单词求平均长度(单词的长度=字符数)

  • map输出时,判断单词的首字符是否是字母,如果不是,则不输出;

  • partition按照单词的首字母进行分区, 这样就可以得到26个分区;

  • combine内求每个单词的次数和(map端的输出结果求和);

  • 定义26个reduce,reduce0只接收以字母a开头的单词,reduce1只接受以字母b开头的单词;educe内求总长和总次数(每个单词的总长=该单词长度*该单词次数),总长=每个单词的总长和,总次数=每个单词的次数和

  • reduce的cleanup中:reduce输出平均长度=总长/总次数
    这样,得到了26个结果文件,每个文件里只有一个数,即以某字母开头的单词的平均长度。

3、详细代码

3.1 Map端函数

public static class MapClass extends Mapper<LongWritable, Text, Text, IntWritable>
  {
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();
    /**
     * 重载了默认的map方法,利用MapClass将每行字符串拆成单词,然后将输出结果<单词,1>写入到OutputCOllector中
     * OutputCollector由Hadoop框架提供,负责收集Map和Reduce的输出数据
     * 实现Map函数时,只要将输出写入OutputCollector即可
     * map输入的value即为一行的字符串
     */
    public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException 
    {
      StringTokenizer itr = new StringTokenizer(value.toString());//以空格作为分隔符
      while (itr.hasMoreTokens()) 
      {
        String tmp = itr.nextToken();
        if(tmp.charAt(0)>='a' && tmp.charAt(0)<='z' || tmp.charAt(0) >='A' && tmp.charAt(0)<='Z')
        {
            word.set(tmp.toLowerCase());
            context.write(word, one);//把处理结果<单词,1>写入
        }
      }
    }
  }

3.2 Partition函数

public static class PartitionClass extends Partitioner<Text,IntWritable>
  {
    public int getPartition(Text key, IntWritable value, int numPartitions) //numPartitions参数值从主函数中的job.setNumReduceTasks(26)获得
    {
        int result = 0;
        result = (key.charAt(0) - 'a') % numPartitions;
        return result;
    }
  }

3.3 Combinner函数

  public static class CombinerClass extends Reducer<Text,IntWritable,Text,IntWritable> 
  {
    private IntWritable result = new IntWritable();
    /**
     * 重载了默认的Reduce方法,通过key遍历其values, 并加以处理 
     * 所谓的处理就是将相同key下的词频相加,就可以得到这个单词的总的出现次数
     * reduce中的key是任务的输出结果KEY,即表示一个单词,value即相同key下的数值
     */
    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);//写输出结果
    }
  }

3.4 Reduce函数理解

 public static class ReduceClass extends Reducer<Text,IntWritable,Text,DoubleWritable> 
  {
//    以下成员变量在reduce作业执行全程都起作用
      private int sumLength = 0;
      private int sumCount = 0;
      private double avgLength = 0;

      public void reduce(Text key, Iterable values,Context context) throws IOException, InterruptedException 
      {
          int count = 0;
          int length = key.getLength();

          for (IntWritable val : values) 
          {
              count += val.get();//对相同的单词数量进行累加
          }
          sumLength += length * count;
          sumCount += count;
      }
      protected void cleanup(Context context) throws IOException, InterruptedException 
      {
          if (sumCount != 0)
          {
              avgLength = (double)sumLength / sumCount;
          }
          context.write(new Text("AverageLength"), new DoubleWritable(avgLength));
      }  

  }

3.5 作业提交

public static void main(String[] args) throws Exception 
  {
    Configuration conf = new Configuration();//启用默认设置,Configuration对象封装了客户端服务器的配置
    String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();//获取主函数参数
    if (otherArgs.length != 2) 
    {
      System.err.println("Usage: wordcount  ");
      System.exit(2);
    }

    Job job = Job.getInstance(conf);//定义一个作业控制
    job.setJarByClass(WordCount.class);//设置执行类
    job.setNumReduceTasks(26);//定义26个reduce

    job.setMapperClass(MapClass.class);//指定map类型
    job.setCombinerClass(CombinerClass.class);//指定combiner类型
    job.setReducerClass(ReduceClass.class);//指定reduce类型
    job.setPartitionerClass(PartitionClass.class);//指定partition类型

    job.setMapOutputKeyClass(Text.class);//map的输出key类型设置
    job.setMapOutputValueClass(IntWritable.class);//map的输出value类型设置

    //**注意**:没有job.setReduceOutputKeyClass
    job.setOutputKeyClass(Text.class);//设置**最终**的输出类型是Text(有些MapReduce程序是没有Reduce部分的)
    job.setOutputValueClass(DoubleWritable.class);//设置最终的输出类型是DoubleWritable


    //定义输入数据的路径,通过Debug Configuration的Argument设置,该路径可以是单个文件,也可以是目录,此时将目录下所有文件当作输入或符合特定文件模式的一组文件
    FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
    //定义输出数据的路径,通过Debug Configuration的Argument设置,在运行任务前该目录不能存在,否则会报错
    FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
    //每次配置Job的最后一步,System.exit(0)代表正常退出,System.exit(1)代表不正常退出

    //作业提交就是这一句:job.waitForCompletion(true),其含义是提交作业后并监控集群,作业完成后及时获得反馈信息,若作业正常完成就返回0,否则就返回1。
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }

注意:客户端就是指MapReduce程序。

你可能感兴趣的:(Hadoop)