手记

Hadoop大数据入门到实战(第七节)- Mapreduce的使用

MapReduce是Hadoop的核心功能之一,我们首先需要弄明白MapReduce到底是个啥,是干啥子用滴才行。

什么是MapReduce

MapReduce是一种可用于数据处理的编程模型,我们现在设想一个场景,你接到一个任务,任务是:挖掘分析我国气象中心近年来的数据日志,该数据日志大小有3T,让你分析计算出每一年的最高气温,如果你现在只有一台计算机,如何处理呢?我想你应该会读取这些数据,并且将读取到的数据与目前的最大气温值进行比较。比较完所有的数据之后就可以得出最高气温了。不过以我们的经验都知道要处理这么多数据肯定是非常耗时的。

如果我现在给你三台机器,你会如何处理呢?看到下图你应该想到了:最好的处理方式是将这些数据切分成三块,然后分别计算处理这些数据(Map),处理完毕之后发送到一台机器上进行合并(merge),再计算合并之后的数据,归纳(reduce)并输出。

这就是一个比较完整的MapReduce的过程了。

image.png

如何使用MapReduce进行运算

我们通过一个示例,来体验Map/Reduce的使用。

我们从一个问题入手:目前我们想统计两个文本文件中,每个单词出现的次数。

首先我们在当前目录下创建两个文件:

创建file01输入内容:

Hello World Bye World

创建file02输入内容:

Hello Hadoop Goodbye Hadoop

将文件上传到HDFS的/usr/input/目录下:

不要忘了启动DFS:
start-dfs.sh

然后创建文件夹并上传:

image.png

编写,文件WordCount.java,添加如下内容:

public class WordCount { public static class TokenizerMapper 
       extends Mapper<LongWritable, Text, Text, IntWritable>{    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<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 {
    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目录下已经生成了文件。

image.png

我们来查看part--r-00000文件的内容:

image.png

可以看到统计的数据已经生成在文件中了。

如果你还想要运行一次,那么你需要删除输出路径的文件夹和文件。

代码解释

示例中,Map/Reduce程序总共分为三块即:Map,Recude,JobMap负责处理输入文件的内容,

image.png

TokenizerMappermap方法,它通过StringTokenizer 以空格为分隔符将一行切分为若干tokens,之后,输出< <word>, 1>形式的键值对。

对于示例中的第一个输入,map输出是:
< Hello, 1>
< World, 1>
< Bye, 1>
< World, 1>

第二个输入,map输出是:
< Hello, 1>
< Hadoop, 1>
< Goodbye, 1>
< Hadoop, 1>

WordCount还指定了一个combiner。因此,每次map运行之后,会对输出按照key进行排序,然后把输出传递给本地的combiner(按照作业的配置与Reducer一样),进行本地聚合

image.png

第一个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(本例中就是单词)出现的次数求和。

image.png

因此这个作业的输出就是:
< 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对象:

image.png

在编写MapReduce程序时,要编写一个类继承Mapper类,这个Mapper类是一个泛型类型,它有四个形参类型,分别制定了map()函数的输入键,输入值,和输出键,输出值的类型。就第一关的例子来说,输入键是一个长整型,输入值是一行文本,输出键是单词,输出值是单词出现的次数。

image.png

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函数的输出类型也必须是TextIntWritable类型,即分别输出单词和次数。

image.png

Job类

image.png

一般我们用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

根据输入文件file1file2合并得到的输出文件file3的样例如下:

20150101 x
20150101 y
20150102 y
20150103 x
20150104 y
20150104 z
20150105 y
20150105 z
20150106 x

做个练习题巩固一下吧。



作者:MasterXiao
链接:https://www.jianshu.com/p/5427c096ea3f


0人推荐
随时随地看视频
慕课网APP