MapReduce作业的执行流程、错误处理机制、任务执行

一、MapReduce作业的执行流程

MapReduce作业的执行流程:代码编写→作业配置→作业提交→Map任务的分配和执行→处理中间结果→Reduce任务的分配和执行→作业完成。

每个任务的执行过程中又包含:输入准备→任务执行→输出结果。

MapReduce作业的执行流程、错误处理机制、任务执行_第1张图片

MapReduce作业的执行可以分为11个步骤,涉及4个独立的实体。它们在MapReduce执行过程中的主要作用是:

  • 客户端(Client):编写MapReduce代码,配置作业,提交作业;
  • JobTracker:初始化作业,分配作业,与TaskTracker通信,协调整个作业的执行;
  • TaskTracker:保持与JobTracker的通信,在分配的数据片段上执行Map或Reduce任务,需要注意的是,Hadoop集群中可以包含多个TaskTracker;
  • HDFS保存作业的数据、配置信息等,保存作业结果

1、提交作业

(1)作业提交之前的准备工作

一个MapReduce作业在提交到Hadoop上之后,会进入完全地自动化执行过程。所以在作业提交之前,用户需要将所有应该配置的参数配置完毕。需要配置的主要内容有:

  • 程序代码:这里主要是指Map和Reduce函数的具体代码。
  • Map和Reduce接口的配置:调用map()和reduce()需要配置它们的四个参数,分别是输入key的数据类型、输入value的数据类型、输出key-value对的数据类型和context实例
  • 输入输出路径:作业提交之前,还需要在主函数中配置MapReduce作业在Hadoop集群上的输入路径和输出路径。
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, newPath(otherArgs[1]));
  • 其他类型设置,比如调用runJob方法:先要在主函数中配置如Output的key和value类型、作业名称、InputFormat和OutputFormat等,最后再调用JobClient的runJob方法。

配置完作业的所有内容并确认无误之后就可以运行作业了,也就是执行图6-1中的步骤①

(2)提交作业

作业提交的过程:

  • 用户程序调用JobClient的runJob方法,在提交JobConf对象之后,runJob方法会先行调用JobSubmissionProtocol接口所定义的submitJob方法,并将作业提交给JobTracker。【这一步最重要】
  • 紧接着,runJob不断循环,并在循环中调用JobSubmissionProtocol的getTaskCompletionEvents方法,获取TaskCompletionEvent类的对象实例,了解作业的实时执行情况
  • 如果发现作业运行状态有更新,就将状态报告给JobTracker(管理和调度MapReduce的Job
  • 作业完成后,如果成功则显示作业计数器否则,将导致作业失败的错误记录到控制台

整个提交过程包含以下步骤:

  • 1)通过调用JobTracker对象的getNewJobId()方法从JobTracker处获取当前作业的ID号(见图6-1中的步骤②)。
  • 2)检查作业相关路径。
  • 3)计算作业的输入划分,并将划分信息写入Job.split文件,如果写入失败就会返回错误。
  • 4)将运行作业所需要的资源—包括作业JAR文件、配置文件和计算所得的输入划分等—复制到作业对应的HDFS上(见图6-1的步骤③)。
  • 5)调用JobTracker对象的submitJob()方法来真正提交作业,告诉JobTracker作业准备执行(见图6-1的步骤④)。

2、初始化作业

在客户端用户作业调用JobTracker对象的submitJob()方法后,JobTracker会进行调度,默认的调度方法是FIFO调度方式。当客户作业被调度执行时,JobTracker会创建一个代表这个作业的JobInProgress对象,并将任务和记录信息封装到这个对象中,以便跟踪任务的状态和进程。接下来JobInProgress对象的initTasks函数会对任务进行初始化操作(见图6-1的步骤⑤)。

初始化过程主要有以下步骤:

  • 1)从HDFS中读取作业对应的job.split(见图6-1的步骤⑥),得到输入数据的划分信息,为后面初始化过程中Map任务的分配做好准备。
  • 2)创建并初始化Map任务和Reduce任务。initTasks先根据输入数据划分信息中的个数设定Map Task的个数,根据JobConf中的mapred.reduce.tasks属性来设置reduce task的个数。
  • 3)最后就是创建两个初始化Task,根据个数和输入划分已经配置的信息,并分别初始化Map和Reduce。

3、分配任务

TaskTracker和JobTracker之间的通信和任务的分配是通过心跳机制完成的。

TaskTracker作为一个单独的JVM执行一个简单的循环,主要实现每隔一段时间向JobTracker发送心跳(Heartbeat):告诉JobTracker此TaskTracker是否存活,是否准备执行新的任务。

JobTracker接收到心跳信息,如果有待分配任务,它就会为TaskTracker分配一个任务,并将分配信息封装在心跳通信的返回值中返回给TaskTracker。

TaskTracker从心跳方法的Response中得知此TaskTracker需要做的事情,如果是一个新的Task则将它加入本机的任务队列中(见图6-1的步骤⑦)。

任务分配的详细过程及在此过程中TaskTracker和JobTracker的通信:

  • TaskTracker首先发送自己的状态(主要是Map任务和Reduce任务的个数是否小于上限),并根据自身条件选择是否向JobTracker请求新的Task,最后发送心跳。
  • JobTracker接收到TaskTracker的心跳后首先分析心跳信息,如果发现TaskTracker在请求一个Task,那么任务调度器就会将任务和任务信息封装起来返回给TaskTracker。
  • TaskTracker有固定数量的Map任务和Reduce任务任务槽。当TaskTracker从JobTracker返回的心跳信息中获取新的任务信息时,它会将Map任务或者Reduce任务加入对应的任务槽中。
  • 在JobTracker为TaskTracker分配Map任务时,为了减小网络带宽,会考虑将map任务数据本地化。它会选取一个距离此TaskTracker map任务最近的输入划分文件分配给此TaskTracker。

4、执行任务

TaskTracker申请到新的任务之后,就要在本地运行任务了。

运行任务的第一步是将任务本地化(将任务运行所必需的数据、配置信息、程序代码从HDFS复制到TaskTracker本地,见图6-1的步骤⑧)。

主要通过下面几个步骤来完成任务的本地化:

  • 1)将job.split复制到本地;
  • 2)将job.jar复制到本地;
  • 3)将job的配置信息写入job.xml;
  • 4)创建本地任务目录,解压job.jar;
  • 5)调用launchTaskForJob()方法发布任务(见图6-1的步骤⑨)。

任务本地化之后,通过调用launchTaskForJob()真正启动起来。接下来launchTaskForJob()又会调用launchTask()方法启动任务。代码中可以看出launchTask()方法会先为任务创建本地目录,然后启动TaskRunner。

在启动TaskRunner后,对于Map任务,会启动MapTaskRunner;对于Reduce任务则启动ReduceTaskRunner。之后,TaskRunner又会启动新的Java虚拟机来运行每个任务(见图6-1的步骤⑩)。以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中(见图6-1的步骤11)。

5、更新任务执行进度和状态

一个MapReduce作业在提交到Hadoop上之后,会进入完全地自动化执行过程,用户只能监控程序的执行状态和强制中止作业。所以对于用户而言,能够得知作业的运行状态是非常重要的。

MapReduce作业的进度由下面几项组成:Mapper(或Reducer)读入或写出一条记录,在报告中设置状态描述,增加计数器,调用Reporter对象的progess()方法。

由MapReduce作业分割成的每个任务中都有一组计数器,它们对任务执行过程中的进度组成事件进行计数。如果任务要报告进度,它便会设置一个标志以表明状态变化将会发送到TaskTracker上。另一个监听线程检查到这标志后,会告知TaskTracker当前的任务状态。同时,TaskTracker在每隔5秒发送给JobTracker的心跳中封装任务状态,报告自己的任务执行状态。

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

6、完成作业

所有TaskTracker任务的执行进度信息都会汇总到JobTracker处,当JobTracker接收到最后一个任务的已完成通知后,便把作业的状态设置为“成功”。然后,JobClient也将及时得知任务已成功完成,它会显示一条信息告知用户作业已完成,最后从runJob()方法处返回(在返回后JobTracker会清空作业的工作状态,并指示TaskTracker也清空作业的工作状态)。

二、错误处理机制

1、硬件故障

从MapReduce任务的执行角度出发,所涉及的硬件主要是JobTracker和TaskTracker(对应从HDFS出发就是NameNode和DataNode)。显然硬件故障就是JobTracker机器故障和TaskTracker机器故障。

在Hadoop集群中,任何时候都只有唯一一个JobTracker。所以JobTracker故障就是单点故障,这是所有错误中最严重的。通过创建多个备用JobTracker节点,在主JobTracker失败之后采用领导选举算法来重新确定JobTracker节点。

TaskTracker故障的解决办法主要是重新执行任务。TaskTracker会不断地与系统JobTracker通过心跳机制进行通信。如果某TaskTracker出现故障或运行缓慢,它会停止或者很少向JobTracker发送心跳,那么JobTracker会将此TaskTracker从等待任务调度的TaskTracker集合中移除,同时JobTracker会要求此TaskTracker上的任务立刻返回。

如果此TaskTracker任务是仍然在mapping阶段的Map任务,那么JobTracker会要求其他的TaskTracker重新执行所有原本由故障TaskTracker执行的Map任务

如果任务是在Reduce阶段的Reduce任务,那么JobTracker会要求其他TaskTracker继续执行故障TaskTracker未完成的Reduce任务

2、任务失败

在实际任务中,MapReduce作业还会遇到用户代码缺陷或进程崩溃引起的任务失败等情况。

用户代码缺陷会导致它在执行过程中抛出异常。此时,任务JVM进程会自动退出,并向TaskTracker父进程发送错误消息,同时错误消息也会写入log文件,最后TasKTracker将此次任务尝试标记失败

对于进程崩溃引起的任务失败,TaskTracker的监听程序会发现进程退出,此时TaskTracker也会将此次任务尝试标记为失败。对于死循环程序或执行时间太长的程序,由于TaskTracker没有接收到进度更新,它也会将此次任务尝试标记为失败,并杀死程序对应的进程。

在以上情况中,TaskTracker将任务尝试标记为失败之后会将TaskTracker自身的任务计数器减1以便向JobTracker申请新的任务。TaskTracker也会通过心跳机制告诉JobTracker本地的一个任务尝试失败。JobTracker接到任务失败的通知后,通过重置任务状态,将其加入到调度队列来重新分配该任务执行。如果此任务尝试了4次(可设置)仍没有完成,就不会再被重试,此时整个作业也就失败了。

3、作业调度机制

(1)先进先出(FIFO, First Input FirstOutput)调度算法

在0.19.0版本之前,Hadoop集群上的用户作业采用先进先出(FIFO, First Input FirstOutput)调度算法,即按照作业提交的顺序来运行。同时每个作业都会使用整个集群,因此只有轮到自己运行才能享受整个集群的服务。FIFO调度器不支持优先级抢占,所以这种单用户的调度算法不符合云计算中采用并行计算来提供服务的宗旨。

(2)公平调度器(Fair SchedulerGuide)

从0.19.0版本开始,Hadoop还提供了支持多用户同时服务集群资源公平共享的调度器,即公平调度器(Fair SchedulerGuide)和容量调度器(Capacity Scheduler Guide)。

公平调度是为作业分配资源的方法,其目的是随着时间的推移,让提交的作业获取等量的集群共享资源,让用户公平地共享集群。具体做法是:

  • 当集群上只有一个作业在运行时,它将使用整个集群;
  • 当有其他作业提交时,系统会将TaskTracker节点空闲时间片分配给这些新的作业,并保证每一个作业都得到大概等量的CPU时间
  • 公平调度器按作业池来组织作业,它会按照提交作业的用户数目将资源公平地分到这些作业池里。默认情况下,每一个用户拥有一个独立的作业池,以使每个用户都能获得一份等同的集群资源而不会管它们提交了多少作业。在每一个资源池内,会用公平共享的方法在运行作业之间共享容量。
  • 除了提供公平共享方法外,公平调度器还允许为作业池设置最小的共享资源。设置了最小共享资源的作业池,如果包含了作业,它至少能获取到最小的共享资源。但是如果最小共享资源超过作业需要的资源时额外的资源会在其他作业池间进行切分
  • 公平调度器也支持作业抢占。如果新的作业在规定时间内还未获取公平的资源分配,公平调度器就会允许这个作业抢占已运行作业中的任务,以获取运行所需要的资源。另外,如果作业在超时时间内获取的资源不到公平共享资源的一半时也允许对任务进行抢占
  • 而在选择时,公平调度器会在所有运行任务中选择最近运行起来的任务,这样浪费的计算相对较少。抢占不会导致被抢占的作业失败,只是让被抢占作业的运行时间更长。
  • 最后,公平调度器还可以限制每个用户和每个作业池并发运行的作业数量。这个限制可以在用户一次性提交数百个作业或当大量作业并发执行时用来确保中间数据不会塞满集群上的磁盘空间超出限制的作业会被列入调度器的队列中进行等待,直到早期作业运行完毕。公平调度器再根据作业优先权和提交时间的排列情况从等待作业中调度即将运行的作业。

4、Shuffle和排序

为了让Reduce可以并行处理Map结果,必须对Map的输出进行一定的排序和分割,然后再交给对应的Reduce,而这个将Map输出进行进一步整理并交给Reduce的过程就成为了shuffle。

shuffle过程的性能与整个MapReduce的性能直接相关。总体来说,shuffle过程包含在Map和Reduce两端中:

Map端的shuffle过程是对Map的结果进行划分(partition)、排序(sort)和分割(spill),然后将属于同一个划分的输出合并在一起(merge)并写在磁盘上,同时按照不同的划分将结果发送给对应的Reduce

Reduce端又会将各个Map送来的属于同一个划分的输出进行合并(merge),然后对merge的结果进行排序,最后交给Reduce处理。

5、Map端

Map的输出结果是由collector处理的,所以Map端的shuffle过程包含在collect函数对Map输出结果的处理过程中,Map函数的输出内存缓冲区是一个环形结构。

final int kvnext=(kvindex+1)%kvoffsets.length;

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

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

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

6、Reduce端

在Reduce端,shuffle阶段可以分成三个阶段:复制Map输出、排序合并和Reduce处理。下面按照这三个阶段进行详细介绍:

如前文所述,Map任务成功完成后,会通知父TaskTracker状态已更新,TaskTracker进而通知JobTracker(这些通知在心跳机制中进行)。所以,对于指定作业来说,JobTracker能够记录Map输出和TaskTracker的映射关系

Reduce会定期向JobTracker获取Map的输出位置一旦拿到输出位置,Reduce任务就会从此输出对应的TaskTracker上复制输出到本地(如果Map的输出很小,则会被复制到执行Reduce任务的TaskTracker节点的内存中,便于进一步处理,否则会放入磁盘),而不会等到所有的Map任务结束。这就是Reduce任务的复制阶段

在Reduce复制Map的输出结果的同时,Reduce任务就进入了合并(merge)阶段。这一阶段主要的任务是将从各个Map TaskTracker上复制的Map输出文件(无论在内存还是在磁盘)进行整合,并维持数据原来的顺序。

reduce端的最后阶段就是对合并的文件进行reduce处理。

三、任务执行

1、推测式执行

推测式执行是指当作业的所有任务都开始运行时,JobTracker会统计所有任务的平均进度,如果某个任务所在的TaskTracker节点由于配置比较低或CPU负载过高,导致任务执行的速度比总体任务的平均速度要慢,此时JobTracker就会启动一个新的备份任务,原有任务和新任务哪个先执行完就把另外一个kill掉,这就是经常在JobTracker页面看到任务执行成功、但是总有些任务被kill的原因

MapReduce将待执行作业分割成一些小任务,然后并行运行这些任务,提高作业运行的效率,使作业的整体执行时间少于顺序执行的时间。运行缓慢的任务将成为MapReduce的性能瓶颈。因为只要有一个运行缓慢的任务,整个作业的完成时间将被大大延长。这个时候就需要采用推测式执行来避免出现这种情况。推测式执行的任务只有在一个作业的所有任务开始执行之后才会启动,并且只针对运行一段时间之后、执行速度慢于整个作业的平均执行速度的情况。

推测式执行在默认情况下是启用的(可根据需要关闭)。这种执行方式有一个很明显的缺陷:对于由于代码缺陷导致的任务执行速度过慢,它所启用的备份任务并不会解决问题。除此之外,因为推测式执行会启动新的任务,所以这种执行方式不可避免地会增加集群的负担

2、任务JVM重用

在本章图6-1中可以看出,不论是Map任务还是Reduce任务,都是在TaskTracker节点上的Java虚拟机(JVM)中运行的。当TaskTracker被分配一个任务时,就会在本地启动一个新的Java虚拟机来运行这个任务。

对于有大量零碎输入文件的Map任务而言,为每一个Map任务启动一个Java虚拟机这种做法显然还有很大的改善空间。如果在一个非常短的任务结束之后让后续的任务重用此Java虚拟机,这样就可以省下新任务启动新的Java虚拟机的时间,这就是所谓的任务JVM重用。

需要注意的是,虽然一个TaskTracker上可能会有多个任务在同时运行,但这些正在执行的任务都是在相互独立的JVM上的。TaskTracker上的其他任务必须等待,因为即使启用JVM重用,JVM也只能顺序执行任务。

控制JVM重用的属性默认情况下是1,意味着每个JVM上运行一个任务。可以将这个属性设置为一个大于1的值来启用JVM重用,也可以将此属性设为-1,表明共享此JVM的任务数目不受限制。

3、跳过坏记录

MapReduce作业处理的数据集非常庞大,所以,用户代码在处理数据集中的某个特定记录时可能会崩溃。这个时候即使MapReduce有错误处理机制,但是由于存在这种代码缺陷,即使重新执行4次(默认的最大重新执行次数),这个任务仍然会失败,最终也会导致整个作业失败。所以针对这种由于坏数据导致任务抛出的异常,重新运行任务是无济于事的

最好的办法就是在当前代码对应的任务执行期间,遇到坏记录时就直接跳过去(由于数据集巨大,忽略这种极少数的坏记录是可以接受的),然后继续执行,这就是Hadoop中的忽略模式(skipping模式)

当忽略模式启动时,如果任务连续失败两次,它会将自己正在处理的记录告诉TaskTracker,然后TaskTracker会重新运行该任务并在运行到先前任务报告的记录时直接跳过。从忽略模式的工作方式可以看出,忽略模式只能检测并忽略一个错误记录,因此这种机制仅适用于检测个别错误记录

如果增加任务尝试次数最大值,可以增加忽略模式能够检测并忽略的错误记录数目。默认情况下忽略模式是关闭的,可以使用SkipBadRedcord类单独为Map和Reduce任务启用它。

你可能感兴趣的:(Hadoop,mapreduce,hadoop,大数据)