1.MapReduce作业的执行流程
一个MapReduce作业的执行流程是:代码编写->作业配置->作业提交->Map任务的分配和执行->处理中间结果->Reduce任务的分配和执行->作业完成,而每个任务的执行过程中,又包含输入准备->任务执行->输出结果.
一个MapReduce作业在提交到Hadoop之后会进入完全地自动化执行过程,在这个过程中,用户除了监控程序的执行情况和强制中止作业之外,不能对作业的执行流程进行任何的干预。所以在作业提交之前,用户需要配置一下内容:
(1)程序代码:这里指Map和Reduce函数的具体代码,是MapReduce作业对应的程序必不可少的部分,并且这部分代码的逻辑正确与否与运行结果直接相关。
(2)Map和Reduce接口配置:在MapReduce中,Map接口需要派生自Mapper<k1,v1,k2,v2>接口,而Reduce接口则需要派生自Reduce<k1,v1,k2,v2>接口。在调用这两个方法的时候需要配置它们的四个参数,分别是输入key的数据类型、输入value的数据类型、输出key-value对的数据类型和context实例,其中输入输出的数据类型要与继承时所设置的数据类型相同。还有一个要求就是Map接口的输出key-value类型和Reduce接口的输入key-value类型要对应,因为Map输出组合value之后,它们会变为Reduce的输入内容。
(3)输入输出路径:作业提交之前,还需要在主函数中设置MapReduce作业在Hadoop集群上的输入路径和输出路径(必须保证输出路径不存在,否则会报错)。具体代码如下:
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.addOutputPath(job, new Path(args[0]));
(4)其他类型设置,比如调用runJob方法:先要在主函数中配置 如Outout的key和value类型、作业名称、InputFormat和OutputFormat等,最后再调用JobClient和runJob方法。
1.1.提交作业
配置完作业的所有内容并确认无误之后就可以运行作业了,即执行图中的步骤①。根据JobClient的runJob方法,分析得出.提交作业的流程如下:
(1)通过调用JobTracker对象的getNewJobId()方法从JobTracker处获取当前作业的ID号(图中步骤②)。
(2)检查作业相关路径。在代码中获取各个路径信息时会对作业的对于路径进行检查。比如,检查输出路径是否已经存在,若存在则作业不会被提交,并且会给MApReduce程序返回错误信息;再比如输入目录不存在或没有对于令牌也会返回错误等。
(3)计算作业的输入划分,并将划分信息写入Job.split文件,如果写入失败就会返回错误。split文件的信息主要包括:split文件头、split文件版本号、split的个数。这些信息中的每一条都会包括以下内容:split类型名(默认为FileSplit)、split的大小、split的内容(对于FileSplit来说是写入的文件名,此split在文件中的起始位置上)、split的location信息(即在哪个DataNode上)。
(4)将运行作业所需要的资源复制到对应的HDFS上:作业的JAR文件、配置文件、和计算所得到的输入划分等(图中步骤③)。
(5)调用JobTracker对象的submitJob()方法来真正提交作业,告诉JobTracker作业准备执行(图中步骤④)。
1.2.初始化作业
在客户端用户作业调用JobTracker对象的submitJob()方法之后,JobTracker会把此调用放入内部的TaskTracker变量中,然后进行调度,默认的调度方法是JobQueueTaskScheduler,也就是FIFO调度方法。当客户作业被调度执行时,JobTracker会创建一个代表这个作业的JobInProgress对象,并将任务和记录信息封存到这个对象中,以便跟踪任务的状态和进程。接下来JobInProgress对象的initTask函数会对任务进行初始化操作(图中步骤⑤)。详细步骤如下:
(1)从HDFS中读取作业对应的job.split(图中步骤⑥)。JobTracker会从HDFS中作业对应的路径获取JobClient在步骤③中写入的job.split文件,得到输入数据的划分信息,为后面的初始化过程中Map任务的分配做好准备。
(2)创建并初始化Map任务和Reduce任务。initTasks先根据输入数据划分信息中的个数设定Map Task的个数,然后为每个Map Task生成一个TaskInProgress来处理input split,并将Map Task放入nonRunningMapCache,以便在JobTracker向TaskTracker分配Map Task的时候使用。接下来根据JobConf中的mapred.reduce.tasks属性利用setNumReduceTasks()方法来设置reduce task的个数,然后采用类似Map Task的方法将Reduce Task放入nonRunningReduces中,以便向TaskTracker分配Reduce Task时使用。
(3)最后就是创建两个初始化Task,根据个数和输入划分已经配置的信息,并分别初始化Map和Reduce。
1.3.分配任务
TaskTracker和JobTracker之间的通信和任务分配是通过心跳机制按成的。TaskTracker作为一个单独的JVM执行一个简单的循环,主要实现每隔一段时间向JobTracker发送心跳(Heartbeat),以次告诉JobTracker此TaskTracker是否存活,是否准备执行新的任务。JobTracker接收到心跳信息后,如果有待分配的任务,它就会为TaskTracker分配一个任务,并将分配信息封装在心跳通信的返回值中返回给TaskTracker。TaskTracker从心跳方法的Response中得知此TaskTracker需要做的事情,如果是一个新的Task,则将它加入本机的任务队列中(图中步骤⑦)。
1.4.执行任务
TaskTracker申请到新的任务之后就要在本地运行任务。运行任务的第一步是将任务本地化:将任务运行所必需的数据、配置信息、程序代码从HDFS复制到TaskTracker本地(图中步骤⑧)。这个主要是通过调用localizeJob()方法来完成的。
这个方法主要通过下面结果步骤来完成任务的本地化:
(1)将job.split复制到本地;
(2)将job.jar复制到本地;
(3)将job的配置信息写入job.xml;
(4)创建本地任务目录,解压job.jar;
(5)调用launchTaskForJob()方法发布任务(图中步骤⑨)。
任务本地化之后就可以通过调用launchTaskForJob()真正启动起来。接下来launchTaskForJob()又会调用launchTask()方法启动任务。launchTask()方法会先为任务创建本地目录,然后启动TaskRunner。在启动TaskRunner后,对于Map任务会启动MapTaskRunner;对于Reduce任务则启动ReduceTaskRunner。之后,TaskRunner会启动新的JVM来运行每个任务(图中步骤⑩)。
以Map任务为例,任务执行的简单流程如下:
(1)配置执行参数(获取Java程序的执行环境和配置参数等);
(2)在Child临时文件表中添加Map任务信息(运行Map和Reduce任务的主要进程是Child类);
(3)配置log文件夹,然后配置Map任务的通信和输出参数;
(4)读取input split,生成RecordReader读取数据;
(5)为Map任务生成MapRunnable,依次从RecordReader中接收数据,并调用Mapper的Map函数进行处理;
(6)最后将Map函数的输出调用collect收集到MapOutputBuffer中。
1.5.更新任务执行进度和状态
由MapReduce作业分割成的每个任务中都有一组计数器,它们对任务执行过程中的进度组成时间进行计数。如果人妖要报告进度,它便会设置一个标志以表明状态变化,并将其发送到TaskTracker上。另一个监听线程检查到这个标志后会告知TaskTracker当前的任务状态。同时,TaskTracker在每隔5秒发送给JobTracker的心跳中分装任务状态,报告自己的任务执行状态。
通过心跳机制,所有的TaskTracker的统计信息都会汇总到JobTracker处。JobTracker将这些信息合并起来,产生一个全局作业进度统计信息,用来表明正在运行的所有作业,以及其中所含任务的状态。最后,JobClient通过每秒查看JobTracker来接收作业进度的最新状态。
1.6.完成作业
所有TaskTracker任务的执行进度信息都会汇总到JobTracker处,当JobTracker接收到最后一个任务的已完成通知后,便把作业的状态设置为“成功”。然后,JobClient也将及时得到任务已经完成,它会显示一条信息告知用户作业完成,最后从runJob()方法处返回。
2.错误处理机制
2.1.硬件故障
2.2.任务失败
在实际任务中,MapReduce作业会遇到用户代码缺陷或进程崩溃引起的任务失败等情况.
1.用户代码缺陷会导致它在执行过程中跑出异常,此时,任务JVM进程会自动退出,并向TaskTracker父进程发送错误信息,同时错误消息会写入log文件,最后TaskTracker将此次任务尝试标记失败.
2.对于进程崩溃引起的任务失败,TaskTracker的监听程序会发现进程退出,此时TaskTracker也会将此次任务尝试标记为失败.
3.对于死循环程序或执行时间太长的程序,由于TaskTracker没有接受到进度更新,也会将此次任务尝试标记为失败,并杀死对应的进程.
以上情况中,TaskTracker将任务尝试标记为失败之后,TaskTracker会将自身的任务计数器减1,以便想JobTracker申请新的任务. 同时TaskTracker通过心跳机制告诉JobTracker本地的一个任务尝试失败,JobTracker接收到任务失败通知后,通过重置任务状态,将其加入到调度队列重新分配该任务执行(JobTracker会避免再次分配至失败的TaskTracker).尝试多次(次数可以进行设置)后仍然失败后,则整个作业也失败.
3.作业调度机制
在0.19.0版本之前,Hadoop集群上的用户作业采用先进先出FIFO调度算法,及按照作业提交的顺序来运行.
从0.19.0版本开始,Hadoop另外提供了支持多用户同时服务和集群资源公平共享的调度器,即公平调度器和容量调度器.
3.1.公平调度器
公平调度是为作业分配资源的方法,其目的是随着时间的推移,让提交的作业带回去等量的集群共享资源,让用户公平的共享集群.
公平调度器是按作业池来组织作业,它会按照作业的用户数目将资源公平的分到这些作业池里.
默认情况下,每一个用户拥有一个独立的作业池.每个作业池内,会用公平共享的方法在运行作业之间共享容量.
除了公平共享方法外,公平调度器还允许为作业池设置最小的共享资源,以确保特定用户,群组或生产应用程序总能获得足够的资源.
在常规操作中,当提交一个新作业时,公平调度器会等待已允许作业中的任务完成,已释放时间片给新的作业.同时公平调度器也支持作业抢占.
3.2.容量调度器
4.Shuffle和排序
在MapReduce流程中,为了让Reduce可以并行处理Map结果,必须对Map的输出进行一定的排序和分割,然后在交给对应的Reduce,而这个将Map输出进一步整理并交给Reduce的过程就是成为了shuffle.
shuffle过程的性能与整个MapReduce的性能直接相关.
shuffle过程包含在Map和Reduce两端中.
在Map端的shuffle过程是对Map的结果进行划分(partition),排序(sort)和分割(spill),然后将属于同一个划分的输出合并在一起(merge)并写在磁盘上.同时按照不同的划分将结果发送给对应的Reduce(Map输出的划分与Reduce的对应关系由JobTracker确定).
Reduce端又会将各个Map送来的属于同一个划分的输出进行合并(merge),然后对merge的结果进行排序,最后交给Reduce处理.
4.1.Map端
Map函数的输出内存缓冲区是一个环形结构.当输出内存缓冲区内容达到设定的阈值时,就需要把缓冲区内容分割(spill)到磁盘中.
在collect函数中将缓冲区中的内容写出时,会调用sortAndSpill函数. sortAndSpill每被调用一次就会创建一个spill文件,然后按照key值需要写出的数据进行排序,最后按照划分的顺序将所有需要写出的结果写入这个spill文件中.如果用户作业配置了combiner类,那么在写出过程中会先调用combineAndSpill()在写出,对结果进行进一步合并(combine)
每个Map任务结束之后再Map的TaskTracker上还会执行合并操作(merge).主要目的是将Map生成的众多spill文件中的数据按照划分重新组织,以便于Reduce处理.
4.2.Reduce端
在Reduce端,shuffle阶段可以分成三个阶段:复制Map输出,排序合并和Reduce处理.
Map任务成功完成后,会通知父TaskTracker状态已更新,TaskTracker进而通知JobTracker(通知在心跳机制中进行).
Reduce会定期向JobTracker获取Map的输出位置.一旦拿到输出位置,Reduce任务就会从此输出对应的TaskTracker上复制输出到本地(如果Map的输出很小,则会被复制到执行Reduce任务的TaskTracker节点的内存中,便于进一步处理,否则会放入磁盘),而不会等所有的Map任务结束.
在Reduce复制Map输出结果的同时,Reduce任务就进入合并(merge)阶段.这个阶段主要将从各个Map TaskTracker上复制的Map输出文件(无论在内存还是磁盘)进行合并,并维持原理的顺序.
最后阶段就是对合并文件进行reduce处理.
4.3.shuffle过程的优化
5.任务执行
5.1.推测式执行
推测式执行是指当作业的所有任务都开始运行时,JobTracker会统计所有任务的平均进度,如果某个任务所在的TaskTracker节点由于配置比较低或者其他原因导致任务执行速度比总体任务的平均速度要慢,此时JobTracker就会启动一个新的备份任务,原有任务和新任务哪个先执行完就把另一个kill掉.
推测执行在默认情况下是启动的.通过设置mapred.map.task.speculative.execution和mapred.reduce.tasks.speculative.execution属性的值来为Map和Reduce任务开启或关闭推测式执行.
5.2.任务JVM重用
无论Map任务还是Reduce任务,都是在TaskTracker节点上的Java虚拟机中运行的.
在一个非常短的任务结束后让后续的任务重用此Java虚拟机,这样就可以省下任务启动新JVM的时间,这就是JVM重用.
控制JVM重用的属性是mapred.job.reuse.jvm.num.tasks. 这个属性定义了单个JVM上运行任务的最大数目,默认情况下是1.如果设置为-1,表示共享此JVM的任务数据不受限制.
5.3.跳过坏记录
在代码执行期间,遇到坏记录就直接跳过,然后继续执行,这就是Hadoop的忽略模式.
当忽略模式启动时,如果任务连续失败两次,它会将自己正在处理的记录告诉TaskTracker,然后TaskTracker会重新运行该任务并在运行到先前任务报告的记录时,直接跳过.
通过设置mapred.max.max.attemps和mapred.reduce.max.attemps属性可以增加忽略模式能够检测并忽略的错误记录数目.
默认情况下是关闭的,可以使用SkipBadRedcord类单独为Map和Reduce任务启用它.
5.4.任务执行环境