Hadoop是一个大数据处理平台,目前在大数据领域应用也非常广泛,刚好最近我们BI组在进行把底层数据仓库迁移到Hadoop平台并且当前BI的数据平台已经深度依赖Hadoop平台,所以在工作之余开始去深入了解下Hadoop内部实现以更好地应用它,在遇到问题的时候有更好的解决思路。本文详细介绍了Hadoop领域中分布式离线计算框架MapReduce的原理及源码分析。
1. MapReduce概述
Map/Reduce是一个用于大规模数据处理的分布式计算模型,它最初是由Google工程师设计并实现的,Google已经将它完整的MapReduce论文公开发布了。其中对它的定义是,Map/Reduce是一个编程模型(programmingmodel),是一个用于处理和生成大规模数据集(processing and generating large data sets)的相关的实现。用户定义一个map函数来处理一个key/value对以生成一批中间的key/value对,再定义一个reduce函数将所有这些中间的有着相同key的values合并起来。很多现实世界中的任务都可用这个模型来表达。
2. MapReduce体系结构
图1 MapReduce体系结构
2.1 角色
(1)JobClient:每一个job都会在用户端通过JobClient类将应用程序以及配置参数打包成jar文件存储在HDFS,并把路径提交到JobTracker,然后由JobTracker创建每一个Task(即MapTask和ReduceTask)并将它们分发到各个TaskTracker服务中去执行。
(2)JobTracker:JobTracker是一个master服务, JobTracker负责调度job的每一个子任务task运行于TaskTracker上,并监控它们,如果发现有失败的task就重新运行它。一般情况应该把JobTracker部署在单独的机器上。
(3)TaskTracker:TaskTracker是运行于多个节点上的slaver服务。TaskTracker则负责直接执行每一个task。TaskTracker都需要运行在HDFS的DataNode上。
(4)Task:TaskTracker是运行于多个节点上的slaver服务。TaskTracker则负责直接执行每一个task。TaskTracker都需要运行在HDFS的DataNode上。
2.2 数据结构
(1)Mapper和Reducer:运行于Hadoop的MapReduce应用程序最基本的组成部分包括一个Mapper和一个Reducer类,以及一个创建JobConf的执行程序,在一些应用中还可以包括一个Combiner类,它实际也是Reducer的实现。
(2)JobInProgress:JobClient提交job后,JobTracker会创建一个JobInProgress来跟踪和调度这个job,并把它添加到job队列里。JobInProgress会根据提交的job jar中定义的输入数据集(已分解成FileSplit)创建对应的一批TaskInProgress用于监控和调度MapTask,同时在创建指定数目的TaskInProgress用于监控和调度ReduceTask,缺省为1个ReduceTask。
(3)TaskInProgress:JobTracker启动任务时通过每一个TaskInProgress来launchTask,这时会把Task对象(即MapTask和ReduceTask)序列化写入相应的TaskTracker服务中,TaskTracker收到后会创建对应的TaskInProgress(此TaskInProgress实现非JobTracker中使用的TaskInProgress,作用类似)用于监控和调度该Task。启动具体的Task进程是通过TaskInProgress管理的TaskRunner对象来运行的。TaskRunner会自动装载job jar,并设置好环境变量后启动一个独立的java child进程来执行Task,即MapTask或者ReduceTask,但它们不一定运行在同一个TaskTracker中。
(4)MapTask和ReduceTask:一个完整的job会自动依次执行Mapper、Combiner(在JobConf指定了Combiner时执行)和Reducer,其中Mapper和Combiner是由MapTask调用执行,Reducer则由ReduceTask调用,Combiner实际也是Reducer接口类的实现。Mapper会根据job jar中定义的输入数据集按<key1,value1>对读入,处理完成生成临时的<key2,value2>对,如果定义了Combiner,MapTask会在Mapper完成调用该Combiner将相同key的值做合并处理,以减少输出结果集。MapTask的任务全完成即交给ReduceTask进程调用Reducer处理,生成最终结果<key3,value3>对。这个过程在下一部分再详细介绍。
3. Map-Reduce原理及源码解析
下面就从用户使用Hadoop提交一个MapReduce作业进行MapReduce计算这一过程为线索,详细介绍Task执行的细节,并对Hadoop MapReduce的主要代码进行解析。
3.1 JobClient客户端作业提交
MapReduce的过程首先是由客户端提交一个作业开始的,作业提交过程比较简单,它主要为后续作业执行准备环境,主要涉及创建目录、上传文件等操作,一旦用户提交作业后,JobTracker端便会对作业进行初始化,作业初始化的主要工作是根据输入数据量和作业配置参数将作业分解成若干个MapTask以及ReduceTask,并添加到相关数据结构中,以等待后续被调度执行。如图2,客户端作业提交过程可分为如下几个过程:
(1)用户使用Hadoop提供的Shell命令提交作业;
(2)JobClient按照作业配置信息将作业运行需要的全部文件上传到HDFS上;
(3)JobClient调用RPC接口向JobTracker提交作业;
(4)JobTracker接收到作业后,将其告知TaskScheduler,由TaskScheduler对作业进行初始化。

public static RunningJob runJob(JobConf job) throws IOException {
//首先生成一个JobClient对象
JobClient jc = new JobClient(job);
……
//调用submitJob来提交一个作业
running = jc.submitJob(job);
JobID jobId = running.getID();
while (true) {
//while循环中不断得到此任务的状态,并打印到客户端console中
}
return running;
}
其中JobClient的submitJob函数实现如下:
public RunningJob submitJob(JobConf job) throws FileNotFoundException,
InvalidJobConfException, IOException {
//从JobTracker得到当前任务的id
JobID jobId = jobSubmitClient.getNewJobId();
//准备将任务运行所需要的要素写入HDFS:
//任务运行程序所在的jar封装成job.jar
//任务所要处理的input split信息写入job.split
//任务运行的配置项汇总写入job.xml
Path submitJobDir = new Path(getSystemDir(), jobId.toString());
Path submitJarFile = new Path(submitJobDir, "job.jar");
Path submitSplitFile = new Path(submitJobDir, "job.split");
//此处将-libjars命令行指定的jar上传至HDFS
configureCommandLineOptions(job, submitJobDir, submitJarFile);
Path submitJobFile = new Path(submitJobDir, "job.xml");
//通过input format的格式获得相应的input split,默认类型为FileSplit
InputSplit[] splits =
job.getInputFormat().ge