hadoop自学轨迹-MapReduce运行机制

来源:互联网 发布:理肤泉法国价格知乎 编辑:程序博客网 时间:2024/06/04 19:00

MapReduce工作机制详解

1.MapReduce任务的执行流程

         前面的学习我们知道,一个MapReduce作业的执行流程是:代码编写->作业配置->作业提交->Map任务的分配和执行->处理中间结果->Reduce任务的分配和执行->作业完成。而在每个任务的执行过程中,又包含输入准备->任务执行->输出结果。

         从图中可以看出,MapReduce作业的执行可以分为11个步骤,涉及4个实体,它们的主要作用是:

ü  客户端:编写MapReduce代码,配置作业,提交作业;

ü  JobTrack:初始化作业,分配作业,与TaskTrack通信,协调整个作业的执行;

ü  TaskTrack:保持JobTrack通信,在分配的数据片段上执行Map或Reduce任务;

ü  HDFS:保存作业的数据、配置信息等,保存作业的结果。

 

2.提交作业

         一个MapReduce作业提交到Hadoop上之后,会进入完全的自动化执行过程。在这个过程中,用户除了监控程序的执行情况和强制中止作业之外,不能对作业进行任何的干扰。所以在作业提交之前,用户需要将所有应该配置的参数按照自己的意愿配置完毕。需要配置的内容主要有:

ü  程序代码:指Map函数和Reduce函数

ü  Map接口和Reduce接口的配置:对输入输出的数据类型进行配置,注意的是Map的输出类型要和Reduce的输入类型对应。因为map的输出key-value类型会成为Reduce的输入内容。

ü  输入输出路径:作业提交之前还需要在主函数中配置MapReduce作业在Hadoop集群上的输入路径和输出路径。(必须保证输出路径不存在,如果存在会报错)。具体的代码如下:

FileInputFormat.setInputPaths(job,arg0[0]);

        FileOutputFormat.setOutputPath(job, new Path(arg0[1]));

ü 其他类型设置,比如调用runJob方法:要先在主函数中配置Output的key和value类型、作业名称、InputFormat和OutputFormat等,最后调用JobClient的runJob方法。配置完作业的内容后就可以运行作业了,就是执行图中步骤1,具体的提交方法请看博文:MapReduce计算模型。

    用户程序调用JobClient的runJob方法,在提交JobConf对象之后,runJob方法会先行调用JobSubmissionProtocol接口所定义的submitJob方法,并将作业提交给JobTracker。紧接着,runJob不断循环,并在循环中调用JobSubmissionProtocol的getTaskCompletionEvents方法,获取getTaskCompletionEvent类的对象实例,了解作业的实时执行情况。若果发现作业运行状态有更新,就将状态报告给JobTracker。作业完成后,如果成功就显示作业计算器,否则将导致作业时效的错误记录到控制台。

下面从submitJob()方法的代码出发介绍作业提交的详细过程。


         从上面的代码可以看出,整个提交过程包含以下步骤:

1)通过调用JobTracker对象的getNewJobId()方法从JobTracker处获取当前作业的ID号,对应图中的步骤2

2)检查作业相关路径。在代码中获取各个路径信息的时候会对作业的对应路径进行检查。比如,如果没有指定输出目录或它已经存在,作业就不会被提交,并且会给MapReduce程序返回错误信息,再比如输入目录不存在也会返回错误等。

3)计算作业的输入划分,并将划分信息写入job.split文件,如果写入失败就会返回错误。Split文件的信息主要包括:split文件头、split文件版本号、split个数。这些信息中每一条都会包括以下内容:split类型名、split的大小、split的内容、split的location信息(即在哪个DataNode上)。

4)将运行作业需要的资源-包括作业JAR文件、配置文件和计算所得的输入划分等-复制到作业对应的HDFS上。见图中步骤3。

5)调用JobTracker对象的submitJob()方法来真正提交作业,告诉JobTracker作业准备执行,即图中的步骤4

 

3.初始化作业

         在客户端用户作业调用JobTracker对象的submitJob()方法后,JobTracker会把此调用放入内部的TaskScheduler变量中,然后进行调度,默认的调度方法是JobQueueTaskScheduler,也就是FIFO调度方式。当客户作业被调度执行时,JobTracker会创建一个代表这个作业的JobInProgress对象,并将任务和记录信息封装到这个对象中,以便跟踪任务的状态和进程。接下来JobInProgress对象的initTask是函数会对任务进行初始化操作见图中步骤5

下面从initTask是函数的代码出发详细讲解初始化过程。

 

 

        从上面的代码可以看出在初始化过程中主要有以下步骤:

1)从HDFS中读取作业的job.split,图中步骤6。JobTracker从HDFS中作业对应的路径获取JobClient在步骤3中写入的job.split文件,得到输入数据的划分信息。为后面初始化过程中map任务的分配做好准备。

2)创建并初始化map任务和reduce任务。initTask先根据输入数据划分信息中的个数设定map task的个数,然后为每个maptask生成一个TaskInProgress来处理input split,并将map task 放入nonRunningMapCache中,以便在JobTracker向TaskTracker分配map task的时候使用,接下来根据JobConf中的mapreduce.reduce.task属性利用setNumReducTasks()方法来设置reduce task的个数,然后采用类似map task的方式将reduce task放入nonRunningReduces中,以便在向TaskTracker分配reduce task 的时候使用。

3)最后就是创建两个初始化task,根据个数和输入划分已经配置的信息,并分别初始化map和reduce。

 

4.分配任务

         在前面的介绍中已经知道,TaskTracker和JobTracker之间的同信与任务的分配是通过心跳机制完成的。TaskTracker作为一个单独的jvm执行一个简单的循环,主要实现每隔一段时间向JobTracker发送心跳:告诉JobTracker,此TaskTracker是否存活,是否准备执行新的任务,在JobTracker接收到心跳信息后,如果有待分配的任务,它就会为TaskTracker分配一个任务,并将分配信息封装在心跳通信的返回值中返回给TaskTracker,TaskTracker从心跳方法的Response中得知此TaskTracker需要做的事情,如果是一个新的task则将task加入本机的任务队列中见图步骤7

         下面从TaskTracker中的transmitHeartBead()方法和JobTracker中的heartbeat()方法的主要代码出发,介绍任务分配的详细过程,以及在此过程中,TaskTracker和JobTracker的通信。

 

TaskTracker中transmitHeartBead()方法的主要代码如下:

JobTracker中heartBeat()方法的主要代码如下:

         TaskTracker和JobTracker之间通过心跳通信汇报状态与分配任务的详细过程.TaskTracker首先发送自己的状态(主要是map任务和reduce任务的个数是否小于上限),并根据自身条件选择是否向JobTracker请求新的Task,最后发送心跳.JobTracker接收到TaskTasker的心跳之后首先分析心跳信息,如果发现TaskTracker在请求一个task,那么任务调度器就会将任务和任务信息封装起来返回给TaskTracker.

         针对map任务和reduce任务,TaskTracker有固定数量的任务槽(个数有上限).当TaskTracker从JobTracker的心跳信息中获取新的任务信息时,它会将map任务或者reduce任务加入到对应的任务槽中.为了减少网络带宽会考虑将map任务数据本地化.会根据TaskTracker的网络位置选取一个合适的输入划分文件分配给此TaskTracker.

 

5.执行任务

         在TaskTracker申请到新的任务之后,就要在本地运行任务了.运行任务的第一步是将任务本地化(将任务运行所必需的数据、配置信息、程序代码从HDFS复制到TaskTracker本地,见图步骤8)这主要是通过调用localizedJob()方法来完成的。这个方法主要通过下面几个步骤来完成任务的本地化:

1)将job.split拷贝到本地;

2)将job.jar拷贝到本地;

3)将job的配置信息写入job.xml;

4)创建本地任务目录,解压job.jar

5)调用launchTaskForJob()方法发布任务(见图步骤9

任务本地化之后,launchTaskForJob()方法又会调用launchTask()方法启动任务。代码如下:

         从代码中可以看出launchTask()方法先会为任务创建本地目录,然后启动TaskRunner。在启动TaskRunner后,对于map任务,会启动MapTaskRunner;对于reduce任务则启动ReduceTaskRunner。

         这之后,TaskRunner又会启动新的java虚拟机来运行每个任务见图步骤10以map为例:

1)配置任务执行参数

2)在Child临时文件表中添加map任务信息

3)配置log文件夹,然后配置map任务的通信和输出参数

4)读取input split,生成RecordReader读取数据;

5)为map任务生成MapRunnable,依次从RecordReader中接收数据,并调用Mapper的map函数进行处理;

6)最后将map函数的输出调用collect收集到MapOutputBuffer中见图步骤11

 

6.更新任务执行进度和状态

         通过心跳通信机制,所有TaskTracker的统计信息都会汇总到JobTracker处。JobTracker将这些统计信息合并起来,产生一个全局作业进度统计信息,用来表明正在运行的所有作业,以及其中所含任务的状态,最后JobClient通过每秒查看JobTracker来接收作业进度的最新状态。

 

7.完成作业

         当最后一个任务的已完成通知后,便把作业的状态设置为成功。最后从runJob()方法处返回。

8.Shuffle和排序

         map的输出会经过一个名为shuffle的过程交给reduce处理。shuffle的过程是MapReduce的核心所在,shuffle过程的性能与整个MapReduce的性能直接相关。

         总的来说,shuffle过程包含在map和reduce两端中。在map端的shuffle过程是对map的结果进行划分(partition)、排序(sort)和分割(spill),然后将属于同一个划分的输出合并在一起(merge),并写在磁盘上,同时按照不同的划分将结果发送给对应的reduce(map输出的划分与reduce的对应关系由JobTracker确定)。Reduce端又会将各个map送来的属于同一个划分的输出进行合并,然后对merge的结果进行排序,最后交给reduce处理。下面从map和reduce两端详细介绍shuffle过程。

 

Map端:

从MapReduce的程序中可以看出,map的输出结果是由collector处理的,所以map端的shuffle过程包含在collect函数对map函数输出结果的处理过程中。从具体代码来分析map端的shuffle过程。从collect函数入手:

Final intkvnext = (kvindex + 1)% kvoffsets.length;

当输出内存缓冲区内容达到设定的阙值时,就需要把缓冲区内容分割(spill)到磁盘中了,但是在分割的时候map并不会阻止继续向缓冲区中写入结果,如果map结果生成的速度快于写出速度,那么缓冲区会写满,这时map任务必须等待,直到分割写出过程结束。

在collect函数中将缓冲区中的内容写出时会调用sortAndSpill函数。sortAndSpill函数每被调用一次就会创建一个spill文件,然后按照key值对需要写出的数据进行排序,最后按照划分的顺序将所有需要写出的结果写入这个spill文件中。如果用户作业配置了combiner类,那么在写出过程中会先调用combineAndSpill()再写出,对结果进行进一步的合并是为了让map的输出数据更加紧凑。sortAndSpill函数的执行过程可以参考下面的代码。


显然,直接将每个map生成的众多spill文件交给reduce处理不现实。所以在每个map任务结束之后在map的TaskTracker上还会执行合并操作。这个操作的主要目的是将map生成的众多spill文件中的数据按照划分重新组织,以便于reduce处理。主要做法是针对指定的分区,从各个spill文件中拿出属于同一个分区的所有数据,然后将它们合并在一起,并写入一个已分区且已排序的map输出文件中。待唯一的已分区且已排序的map输出文件写入最后一条记录后,map端的shuffle阶段就结束了。

 

Reduce端

在reduce端,shuffle阶段可以分成三个阶段:复制map输出、排序合并、reduce处理。

Map任务成功完成后,会通知父TaskTracker状态已更新,进而TaskTracker通知JobTracker。所以JobTracker能够记录map输出和TaskTracker的映射关系。Reduce会定期向JobTracker获取map的输出位置。一旦拿到输出位置,reduce任务就会从此输出对应的TaskTracker上复制输出到本地,而不会等到所有的map任务结束。这就是reduce的复制阶段。

在reduce复制map的输出结果的同时,reduce任务就进入了合并阶段。这一阶段主要的任务是将从各个map TaskTracker上复制的map输出文件进行整合,并维持数据原来的顺序。

Reduce端的最后阶段就是对合并的文件进行reduce处理。Reduce TaskTracker从合并的文件中按照顺序先拿出一条数据,交给reduce函数处理,然后直接将结果输出到本地的HDFS上,接着继续拿出下一条数据,再进行处理。

 

9.任务执行

hadoop在任务执行时的具体策略

推测式执行

         MapReduce将待执行作业分割成一些小任务,然后并行运行这些任务,提高作业运行的效率,但运行缓慢的任务将成为MapReduce的性能瓶颈,只要有一个运行缓慢的任务,整个作业完成的时间将被大大延长.采用推测式执行,当JobTracker检测到所有任务中存在运行缓慢的任务,就会启动另一个任务作为备份,原始任务和备份任务只要有一个完成,另一个就会被中止,推测式执行的任务只有在一个作业的所有任务开始执行之后才会启动,并且只针对运行一段时间之后,其执行速度慢于整个作业的平均执行速度的情况.推测式执行任务默认是启动的.

跳过坏记录

         在当前代码对应的任务执行期间,遇到坏记录时就直接跳过去,然后继续执行,这就是Hadoop中的忽略模式.当忽略模式启动时,如果任务失败两次之后,它会将自己正在处理的记录告诉TaskTracker,然后TaskTracker会重新运行该任务并在运行到先前任务报告的记录地方时直接跳过.默认情况下仅适用于检测个别错误记录.而且忽略模式默认是关闭的.可以通过mapred.map.max.attemps和mapred.reduce.max.attemps两个属性决定增加错误记录数目。

任务执行环境

         TaskTracker被分配一个任务时,就会在本地启动一个JVM运行这个任务。所以启动map或reduce task时,会直接从父TaskTracker继承执行环境。并且会根据配置文件创建Job和本地缓存。

0 0