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是一种编程模型,用于大规模数据集的并行运算。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阶段”);③依次读取
图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是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
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权威指南