MapReduce总结

来源:互联网 发布:魔兽世界mac版 编辑:程序博客网 时间:2024/05/08 01:13

简介

MapReduce是一种可用于数据处理的编程模型。MapReduce程序本质上是并行运行的,优势在于处理大规模数据集。本文介绍了Hadoop MapReduce的架构,并结合具体例子介绍MapReduce的工作过程,以更深入的了解MapReduce。

Hadoop MapReduce架构

Hadoop MapReduce采用了Master/Slave(M/S)架构,具体如下图所示。它主要有Client、JobTracker、TaskTracker和Task组件组成。
   

                                                                                图1 MapReduce架构
Client
用户编写的MapReduce程序通过Client提交到JobTracker端;同时,用户可通过Client提供的一些接口查看作业运行状态。在Hadoop内部用“作业”(Job)表示MapReduce程序。一个MapReduce程序可对应若干个作业,而每个作业会被分解成若干个Map/Reduce任务(Task)。
JobTracker
JobTracker主要负责资源监控和作业调度。JobTracker监控所有TaskTracker与作业的健康状况,一旦发现失败情况后,其会将相应的任务转移到其他节点;同时,JobTracker会跟踪任务的执行进度、资源使用量等信息,并将这些信息告诉任务调度器,而调度器会在资源出现空闲时,选择合适的任务使用这些资源。在Hadoop中,任务调度器是一个可插拔的模块,用户可以根据自己的需要设计相应的调度器。
TaskTracker
TaskTracker会周期性地通过Heartbeat将本节点上资源的使用情况和任务的运行进度汇报给JobTracker,同时接收JobTracker 发送过来的命令并执行相应的操作(如启动新任务、杀死任务等)。TaskTracker使用“slot”等量划分本节点上的资源量。“slot”代表计算资源(CPU、内存等)。一个Task获取到一个slot 后才有机会运行,而Hadoop调度器的作用就是将各个TaskTracker上的空闲slot分配给Task使用。slot 分为Map slot和Reduce slot两种,分别供Map Task和Reduce Task使用。TaskTracker通过slot数目(可配置参数)限定Task的并发度。
Task
Task 分为Map Task和Reduce Task两种,均由TaskTracker启动。接下来,我们来详细介绍MapReduce编程模型,并讲解相应的任务。

MapReduce编程模型

MapReduce是一种编程模型,用于大规模数据集的并行运算。Map(映射)和Reduce(化简),采用分而治之思想,先把任务分发到集群多个节点上,并行计算,然后再把计算结果合并,从而得到最终计算结果。多节点计算,所涉及的任务调度、负载均衡、容错处理等,都由MapReduce框架完成,不需要编程人员关心这些内容。
MapReduce任务过程分为两个处理阶段:map阶段和reduce阶段,分别对应Map Task和Reduce Task。
HDFS以固定大小的block为基本单位存储数据,而对于MapReduce而言,其处理单位是split。split是一个逻辑概念,它只包含一些元数据信息,比如数据起始位置、数据长度、数据所在节点等。它的划分方法完全由用户自己决定。但需要注意的是,split的多少决定了Map Task 的数目,因为每个split 会交由一个Map Task处理。Map Task先将对应的split迭代解析成一个个key/value 对,依次调用用户自定义的map() 函数进行处理,最终将临时结果存放到本地磁盘上,其中临时数据被分成若干个partition,每个partition 将被一个Reduce Task处理。Hadoop在存储有输入数据(HDFS中的数据)的节点上运行map任务,可以获得最佳性能。这就是所谓的"数据本地化优化"(data locality optimization), 因为它无需要使用宝贵的集群带宽资源。

                      

                                                                         图2 Map Task执行过程

Reduce Task执行过程如图3所示。该过程分为三个阶段①从远程节点上读取MapTask中间结果(称为“Shuffle阶段”);②按照key 对key/value 对进行排序(称为“Sort阶段”);③依次读取<key, value list>,调用用户自定义的reduce() 函数处理,并将最终结果存到HDFS 上(称为“Reduce阶段”)。

                                 

                                                                     图3 Reduce Task执行过程

每个阶段均定义为一个数据处理函数,分别被称为mapper和reducer; 在map阶段, MapReduce获取输入数据将数据单元转入mapper;在reduce阶段,reducer处理来自mapper的所有输出,并给出结果。每个阶段都以键值对作为输入和输出,其类型由程序员来选择。程序员还需要写两个函数:map函数和reduce函数,这两个函数是MapReduce的核心。它们是交给用户实现的,这两个函数定义了任务本身。
  ● map函数:接受一个键值对(key-value pair),产生一组中间键值对。Map/Reduce框架会将map函数产生的中间键值对里键相同的值传递给一个reduce函数。
  ● reduce函数:接受一个键,以及相关的一组值,将这组值进行合并产生一组规模更小的值(通常只有一个或零个值)。
但是,Map/Reduce并不是万能的,适用于Map/Reduce计算有先提条件:
①待处理的数据集可以分解成许多小的数据集;
②而且每一个小数据集都可以完全并行地进行处理;
若不满足以上两条中的任意一条,则不适合使用Map/Reduce模式。

WordCount实例

WordCount是Hadoop提供的示例代码,用于统计文本中各单词出现的次数。其位于hadoop-mapreduce-examples子项目中,属于org.apache.hadoop.examples包,具体的实现如下:

/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements.  See the NOTICE file * distributed with this work for additional information * regarding copyright ownership.  The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License.  You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.apache.hadoop.examples;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 {  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 {      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();    String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();    if (otherArgs.length < 2) {      System.err.println("Usage: wordcount <in> [<in>...] <out>");      System.exit(2);    }    Job job = Job.getInstance(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);    for (int i = 0; i < otherArgs.length - 1; ++i) {      FileInputFormat.addInputPath(job, new Path(otherArgs[i]));    }    FileOutputFormat.setOutputPath(job,      new Path(otherArgs[otherArgs.length - 1]));    System.exit(job.waitForCompletion(true) ? 0 : 1);  }}
Mapper类是一个泛型类型,它有四个形参类型,分别指定map函数的输入键、输入值、输出键和输出类型。Hadoop本身提供了一套可优化网络序列化传输的基本类型,而不是直接使用Java内嵌的类型。这些类型都在org.apache.hadoop.io包中。map()方法的输入时一个键和一个值,还提供了Context实例用于输出内容的写入。以同样的方法用Reducer来定义reduce函数。


                                                                    图4 WordCount MapReduce过程示意图
代码中,也给出了提交作业的示例代码,具体参考静态main()方法。Job对象指定作业执行规范,可以用它来控制整个作业的运行。在Hadoop集群上运行这个作业时,要把代码打包成一个JAR文件(Hadoop集群上发布这个文件)。不必明确指定JAR文件的名称,在Job对象的setJarByClass()方法传递一个类即可,Hadoop利用这个类来查找包含它的JAR文件,进而找到相关的JAR文件。
构造Job对象之后,需要指定输入和输出数据的路径。调用FileInputFormat类的静态方法addInputPath()来定义输入数据的路径,这个路径可以使单个的文件、一个目录(将目录下所有文件当做输入)或符合特定文件模式的一系列文件。可以多次调用addInputPath()来实现多路径的输入。
调用FileOutputFormat类的静态方法setOutputPath()来指定输出的路径(只能有一个输出路径)。这个方法指定的是reduce函数的输出文件的写入目录。在运行作业前该目录时不应该存在的,否则Hadoop会报错并拒绝运行作业。这种预防措施的目的是防止数据丢失。接着通过setMapperClass()和setReducerClass()指定map类型和reduce类型。在设置定义map和reduce函数的类之后,可以开始运行作业。Job中的waitForCompletion()方法提交作业并等待执行完成。

测试MapReduce作业:
%export HADOOP_CLASSPATH=hadoop-examples.jar
%hadoop WordCount input/sample.txt output
如果调用hadoop命令的第一个参数是类名,Hadoop就会启动一个JVM来运行这个类。使用hadoop命令运行作业比直接使用Java命令来运行更方便,因为前者将Hadoop库文件(及其依赖关系)路径加入到类路径参数中,同时也能获得Hadoop的配置文件。需要定义一个HADOOP_CLASSPATH环境变量用于添加应用程序类的路径,然后由Hadoop脚本来执行相关操作。
下图描述了作业从提交到运行结束经历的整个过程,对于理解Hadoop MapReduce执行过程有一个整体的认识。

图5 MapReduce整体作业示意图

参考资料

1. Hadoop技术内幕 深入解析MapReduce架构设计与实现原理
2. Hadoop权威指南