在Hadoop的学习过程中,最重要的莫过于MapReduce了,所以学好MapReduce的工作机制是我们学好Hadoop的核心。
剖析MapReduce作业运行机制
可以通过一个简单的方法调用来运行MapReduce作业:Job对象的submit()方法。
PS:也可以使用waitForCompletion(),它用于提交以前没有提交过的作业,并等待它的完成。
submit()方法调用封装了大量的处理细节。
图:MapReduce的Job提交流程图
整个过程如图所示,在最高层,有以下5个独立的实体:
● 客户端,提交MapReduce作业
● YARN资源管理器,负责协调集群上计算机资源的分配。
● YARN节点管理器,负责启动和监视集群中机器上的计算机容器(container)(内存,CPU和资源使用情况,定期向resourceManager报告)
● MapReduce的application master,负责协调运行MapReduce作业的任务。它和MapReduce任务在容器中运行,这些容器由资源管理器分配并由节点管理器进行管理。
● 分布式文件系统(一般为HDFS),用来与其他实体间共享作业文件。
作业的提交
Job的submit()方法创建一个内部的JobSummit实例,并且调用其subnitJobInternal()方法(参见图中的步骤1:run job)。提交作业后,waitForCompletion()每秒轮询作业的进度,如果发现上次报告之后有改变,便把进度报告到控制台。作业完成后,如果成功,就显示作业计数器;如果失败,则导致作业失败的错误被记录到控制台。
JobSummiter所实现的作业提交过程如下:
1、向资源管理器请求一个新的应用ID,用于MapReduce作业ID(参见步骤2)
2、检查作业的输出说明。例如:如果没有指定输出目录或者是输出目录已经存在,作业就不提交,错误返回给MapReduce程序(这里也就是为什么在执行一次MapReduce程序之后要把输出目录删除,因为这里会进行检查并将信息返回给控制台)
3、计算作业的输入分片。如果分片无法计算,比如因为输入路径不存在,作业就不提交,错误就返回给MapReduce程序。(这里是要指定输入路径,是为了获取输入分片,如果路径不存在,则无法获取输入分片,程序就会返回一个错误)
4、将作业所需要的资源(包括作业JAR文件、配置文件和计算所得到的输入分片)复制到一个以作业ID命名的目录下的共享文件系统中(参见步骤3)。作业JAR的复本比较多(默认值是10),因此在运行作业的任务时,集群中有很多的复本可供节点管理器访问。
5、通过调用资源管理器的submitApplication()方法提交作业。(参见步骤4)
作业的初始化
资源管理器收到调用它的submitApplication()消息后,便将请求传递给YARN调度器(scheduler)。调度器分配一个容器,然后资源管理器在节点管理器的管理下在容器中启动application master(步骤5a和5b)。
MapReduce作业的application master是一个java应用程序,它的主类是MRAppMaster。由于将接受来自任务的进度和完成报告(步骤6),因此application master对作业的初始化是通过创建多个簿记对象以保持对作业进度的跟踪来完成的。接下来,它接受来自共享文件系统的、在客户端计算的输入分片(步骤7)。然后针对每一个分片创建一个map任务对象以及通过作业的setNumReduceTasks()方法来设置确定的多个reduce任务对象。任务ID在此时分配。
application master必须决定如何运行构成MapReduce作业的各个任务。如果作业很小,就选择和自己在同一个JVM上运行任务,与在一个节点上运行这些任务相比,当application master判断在新的容器中分配和运行任务的开销大于并行运行它们的开销时,就会发生这一情况。这样的作业称为uberized,或者作为uber任务运行。(这样的任务是用运行在本地的JVM上)
哪些作业是小作业?
默认情况下,小作业就是少于10个mapper且只有1个reduce且输入大小小于一个HDFS块的作业。
PS:如果想要明确启动一个任务为Uber任务,具体方法是将mapreduce.job.ubertask.enable设置为true
最后,在任何任务运行之前,application master调用setupJob()方法设置OutputCommitter。FileOutputCommitter为默认值,表示将建立作业的最终输出目录及任务输出的临时工作空间。
任务的分配
如果作业不适合作为Uber任务运行,那么application master就会为该作业中的所有map任务和reduce任务向资源管理器请求容器(步骤8)。首先为Map任务发出请求,该请求的优先级要高于reduce任务请求,这是因为所有的map任务必须在reduce的排序阶段能够启动前完成。直到有5%的map任务已经完成时,为reduce任务的请求才会发出。
reduce任务能够在集群中任意位置运行,但是map任务的请求有着数据本地化局限,这也是调度器所关注的。在理想情况下,任务是数据本地化的,也就是任务在分片驻留的同一节点上运行(我理解的数据本地化是任务在存有这个分片复本的节点上运行)。可选的情况是,任务可能是机架本地化的,即是可以和分片是在同一机架上而非同一节点上运行。有一些任务既不是数据本地化也不是机架本地化,它们会从别的机架,而不是运行所在的机架上获取自己的数据。对于一个特定的作业运行,可以通过查看作业的计数器来确定每个本地化层次上运行的任务的数量。
请求也为任务指定了内存需求和CPU数。在默认情况下,每个map任务和reduce任务都分配到1024MB的内存和一个虚拟的内核。
任务的执行
一旦资源管理器的调度器为任务分配了一个特定节点上的容器,application master就通过与节点管理器通信来启动容器(步骤9a和9b)。该任务由主类为YarnChild的一个java应用程序执行。在它运行任务之前,首先将任务需要的资源本地化,包括作业的配置,JAR文件和所有来自分布式缓存的文件(步骤10)。最后运行map任务或者reduce任务(步骤11)。
每个任务都能够执行搭建(setup)和提交(commit)动作,它们和任务本身在同一个JVM中运行,并由作业的OutputCommiter确定。对于基于文件的作业,提交动作是将任务输出由临时位置搬移到最终位置。提交协议确保当推测执行被启动时,只有一个任务复本被提交,其他的都被取消。
Streaming
Streaming运行特殊的map任务和reduce任务,目的是为运行用户提供的可执行程序,并与之通信。
Streaming任务使用标准输入和输出流与进程进行通信。在任务执行过程中,java进程都会把输入键-值对传给外部的进程,后者通过用户定义的map函数和reduce函数来执行它并把输出键-值对传回java进程。从节点管理器的角度看,就像其子进程自己在运行map或reduce代码一样
进度和状态的更新
MapReduce作业是长时间运行的批量作业,运行时间范围从数秒到数小时。这可能是一个很长的时间段,所以对于用户而言,能够得知关于作业进展的一些反馈是很重要的。一个作业和它的每个任务都有一个状态(status),包括:作业或任务的状态(比如,运行中,完成或者失败)、map和reduce的进度、作业计数器的值,状态消息或描述。这些状态信息在作业期间不断改变,它们是如何与客户端进行通信的呢?
任务在运行时,对其进度保持追踪。对于map任务,任务进度是已处理输入所占的比例。对于reduce任务,情况比较复杂,但是还是估计已处理的reduce输入所占的比例。整个reduce过程分为三部分,与shuffle的三个阶段相对应。比如任务已经执行了reduce一半的输入,那么任务的进度是5/6,这是因为已经完成复制和排序阶段(每个占1/3),并且已经完成reduce阶段的一半。
获取任务进度和状态的方法有哪些?
● 当map任务或reduce任务运行时,子进程和自己的父application master通过umbilical接口通信。每隔3秒,任务通过这个umbilical接口向自己的application master报告进度和状态(包括计数器),application master会形成一个作业的汇聚视图
● 资源管理器的界面显示了所有运行中的应用程序,并且分别有链接指向这些应用各自的application master的界面,这些界面展示了MapReduce作业的更多细节,包括其进度。
● 在作业期间,客户端每秒钟轮询一次application master以接收最新状态。客户端也可以使用Job的getStatus()方法得到一个JobStatus的实例,后者包含作业的所有状态信息。
作业的完成
当application master收到作业最后一个任务已完成的通知后,便把作业的状态设置为“成功”。然后在Job的轮询状态时,便知道任务已经完成,于是Job打印一条消息告知用户,然后从waitForComplrtion()方法返回。Job的统计信息和计数值也在这个时候输出到控制台。
最后,作业完成时,application master和任务容器清理其工作状态(这样中间输出将会被删除),OutputCommitter的commitJob(0方法会被调用。作业的信息由作业历史服务器存档,以便于日后用户需要时可以查询。
失败
在现实情况中,用户代码错误不断,进程崩溃,机器故障等很多在运行过程中出现的错误。使用Hadoop最重要的好处之一就是它能够处理此类故障并让你能够成功完成作业。我们需要考虑以下实体的失败:任务、application master、节点管理器和资源管理器。
任务运行失败
● 最常见的是map任务或reduce任务中的用户代码抛出运行异常。发生这类故障我们应该怎么办?
任务JVM会在退出之前向其父application master发送错误报告。错误报告最后被记入用户日志。application master将此次任务尝试标记为failed(失败),并释放容器以便资源可以为其他任务使用。
对于Streaming任务,如果Streaming进程以非零退出代码退出,则被标记为失败。
● 如果在执行过程中任务JVM突然退出,应该怎么办?
在这种情况下,节点管理器会注意到进程已经退出,并且通知application master将此次任务标记为失败。
● 任务挂起
任务挂起的处理方式不同。一旦application master注意到已经有一段时间没有收到进程的更新,便会将任务标记为失败。然后会将进程杀死。任务被认为失败的超时时间为10分钟。
超时设置为0将关闭超时判定,所以长时间运行的任务将永远不会被标记为失败,这种情况下,被挂起的任务永远不会释放它的容器并随着时间的推移最终会降低集群的效率。因此避免这种设置。
application master在任务失败之后,将重新调度该任务的执行。application master会试图避免在之前失败的节点管理器上执行该任务。此外,在一个任务失败超过4次之后,将不会重试。这个值可以设置(map任务设置和reduce任务设置不在同一个配置文件中,但是默认设置一样,都为4次)。
● 任务尝试
任务尝试也是可以中止的,这与失败不同。任务尝试可以被中止是因为它是一个推测副本或因为它所处的节点管理器失败,导致application master将它上面运行的所有任务标记为killed。被中止的任务不会被计入任务尝试次数。因为尝试被中止不是任务的过错。
PS:推测副本的理解是:在集群运行一个任务时,在一个任务运行比预期慢的时候,他会尽量检测,并且启动另外个相同的任务作为备份,当其中一个已经执行完毕,另外一个任务就会被中止,这就是推测执行。
application master运行失败
● YARN上的YARN application master运行失败应该如何恢复?
在任务执行中,有可能会出现application master失败,这里默认设置是失败两次之后不再进行尝试。在application master失败之后,恢复的过程如下:
application master会向资源管理器发送周期性心跳,当application master失败时,资源管理器将会检测到该失败并且在一个新的容器(由节点管理器管理)中开始一个新的master实例。对于MapReduce application master,它将使用作业历史来恢复失败的应用程序所运行的任务的状态,使任务不必重头运行。
节点管理器运行失败
● 如果节点管理器崩溃或者运行非常缓慢而失败应该怎么办?
出现上述现状,节点管理器就会停止向资源管理器发送心跳信息(或者发送的频率很慢)。如果10分钟内没有收到一条心跳信息,资源管理器就会通知停止发送心跳信息的节点管理器,并且将其从自己的节点池中移除以调度启用容器。
另外对于那些曾经在失败的节点管理器上运行且成功完成的map任务,如果属于未完成的作业,那么application master会安排它们重新运行。这是由于这些任务的中间输出驻留在失败的节点管理器的本地文件系统中,可能无法被reduce任务访问的缘故。
● 节点管理器被拉黑的情况
如果在一个节点上出现运行失败的次数比较多,那么节点管理器可能会被拉黑,即使节点管理器本身可能没有失败过。由application master管理黑名单,对于MapReduce,如果在一个节点管理器上有超过3个任务失败,application master就会尽量将任务调度到不同的节点上。
资源管理器运行失败
资源管理器失败是一件很严重的事情,因为没有资源管理器,作业和任务容器将无法启动,在机器失败的情况下,所有的作业和任务都会失败而且无法恢复。● 资源管理器的高可用
为获得高可用性(HA),那么在双机热备份配置下,运行一对资源管理器是有必要的。如果主资源管理器失败了,那么备份资源管理器能够接替,且客户端不会感觉到明显的中断。
关于所有运行中的应用程序的信息存储在一个高可用的状态存储区中(由ZooKeeper或者HDFS备份),这样备机可以恢复出失败的主资源管理器的关键状态。节点管理器信息没有存储在状态存储区中,因为节点管理器发送它们的第一个心跳信息时,节点管理器的信息能以相当快的速度被新的资源管理器重构
当新资源管理器启动后,它从状态存储区中读取应用程序的信息,然后为集群中运行的所有应用程序重启application master。这里不会重启记为失败的应用程序的application master,这是因为不是因为程序代码错误而失败而是因为被系统强行中止的。
实际情况是:application master重启不是MapReduce应用程序的问题,因为它们是恢复已完成的任务的工作。
资源管理器从备机到主机的切换时由故障转移控制器(failover controller)处理的。默认的故障转移控制器是自动工作的,使用ZooKeeper的leader选举机制以确保同一时刻只有一个主资源管理器。不同于HDFS的高可用的实现,故障转移控制器不必是一个独立的进程,为配置方便,默认情况下嵌入在资源管理器中。(故障转移控制器也可以配置为手动处理,但是不建议这样使用)
为应对资源管理器的故障转移,必须对客户和节点管理器进行配置,因为它们可能是在和两个资源管理器打交道。客户端和节点管理器以轮询的方式尝试连接每一个资源管理器,直到找到主资源管理器。如果主资源管理器故障,它们将再次尝试直到备份资源管理器变为主机。