从high-level来看主要有5个独立的实体,详细见下表。
名称
|
功能
|
---|---|
The client | 提交MapReduce job |
The YARN resource manager | 协调(coordinate)集群上分配的计算资源 |
The YARN node manager | 启动并监控在集群上的计算容器 |
The MapReduce application master | 协调(coordinate)运行MapReduce job 的task |
The distributed filesystem(HDFS) | 存储文件供其他实体共享job 文件 |
MapReduce任务由YARN resource manager调度,由YARN node manager管理(managed)。
ResourceManager:是YARN资源控制框架的中心模块,负责集群中所有的资源的统一管理和分配。它接收来自NM(NodeManager)的汇报,建立AM,并将资源派送给AM(ApplicationMaster)。
NodeManager:简称NM,NodeManager是ResourceManager在每台机器的上代理,负责容器的管理,并监控他们的资源使用情况(cpu,内存,磁盘及网络等),以及向 ResourceManager提供这些资源使用报告。
ApplicationMaster:以下简称AM。YARN中每个应用都会启动一个AM,负责向RM申请资源,请求NM启动container,并告诉container做什么事情。
Container:资源容器。YARN中所有的应用都是在container之上运行的。AM也是在container上运行的,不过AM的container是RM申请的。
1. Container是YARN中资源的抽象,它封装了某个节点上一定量的资源(CPU和内存两类资源)。
2. Container由ApplicationMaster向ResourceManager申请的,由ResouceManager中的资源调度器异步分配给ApplicationMaster;
3. Container的运行是由ApplicationMaster向资源所在的NodeManager发起的,Container运行时需提供内部执行的任务命令(可以是任何命令,比如java、Python、C++进程启动命令均可)以及该命令执行所需的环境变量和外部资源(比如词典文件、可执行文件、jar包等)。
另外,一个应用程序所需的Container分为两大类,如下:
(1) 运行ApplicationMaster的Container:这是由ResourceManager(向内部的资源调度器)申请和启动的,用户提交应用程序时,可指定唯一的ApplicationMaster所需的资源;
(2) 运行各类任务的Container:这是由ApplicationMaster向ResourceManager申请的,并由ApplicationMaster与NodeManager通信以启动之。
以上两类Container可能在任意节点上,它们的位置通常而言是随机的,即ApplicationMaster可能与它管理的任务运行在一个节点上。
整个MapReduce的过程大致分为 Map-->Shuffle(排序)-->Combine(组合)–>Reduce
下图表面Hadoop如何跑 MapReduce job
job提交时做的事情:
1.向resource manager 申请一个新的application ID,作为MapReduce job ID(上图中的step 2)
2.检查输出路径。如果输出路径已经存在,job将不会被提交,并且抛出错误。(因为MapReduce几乎都是比较耗时的任务,所以越早发现问题越好)
3.input划分。如果不能正常划分的话(如输入路径不存在,划分的小文件太多等原因),job将不会被提交,并且抛出错误。
4.拷贝运行job所需的资源。拷贝包括job JAR file、配置文件、划分好的输入数据到共享的文件系统中根据job ID命名的directory(上图中step 3)(其中jar包可能会有多个拷贝,可通过配置mapreduce.client.submit.file.replication来控制)
5.调用submitApplication()方法来提交任务给resource manager(上图中step 4)
当resource manager 接收到提交的job时,它将job转给YARN scheduler进行调度,scheduler将为job分配一个容器,并启动application的主进程,此进程由node manager管理(上图中step 5a 和step 5b )。
为每一个split创造一个个map。
task object 并创建一定数量的reduce task object(由mapreduce.job.reduces 属性决定)同时task id此时被分配。
如果job足够小,application master将选择在自己的JVM中运行此Job。这种类型的job叫做uberized or run as an uber task。
如果job不满足uberize条件(small job 少于10个mapper 只有1个reducer 并且输入小于一个HDFS block)时,application master想resource manager申请来运行所有map和reduce的容器(上图中step 8)。其中map tasks 先分配,即比reduce的优先级要高,因为所有的map 任务要在sort阶段之前进行完,,比reduce要早。直到map任务完成5%才开始给reduce分配容器。
一旦task在某个特定的node上由resource manager 调度分配了执行时所需的资源,AP(application master)就启动容器并与node manager通信(step 9a step 9b)。
任务将由主类叫YarnChild的java application执行。
在task执行之前,它所需的资源(包括job configuration JAR files等)将会被本地化,也就是拷贝到执行它的node上,然后开始执行map或者reduce(step11)
YarnChild跑在一个专用的JVM上边,所以用户程序的bugs将不会影响到node manager。
MapReduce jobs 一般都是执行时间很长的,所以用户在其执行时候得到其执行的进度和状态的反馈就变得很重要。一个job以及这个job下的每一个task都有它的状态(status)包含:
1. job或task的state(running,successfully,completed,failed)
2. maps和reduces的进度
3.job counters的值
4.用户设定的状态信息和描述
上述的状态会随着job的执行而跟着改变,那么他们是怎样被反馈给client的呢?
当task执行的时候,它跟踪它自己的进度。对于map tasks进度就是它已经被处理的输入的比值。比如有一半的输入被map程序处理过了,反馈给用户的就是map 50%
对于reduce任务,就稍微复杂一些。它将整个程序划分为三个阶段,分别对应shuffle的三个阶段。举例来说:如果task reduce了一半的输入数据,那么此reduce的进程就是5/6 包括copy and sort phases(分别1/3),以及reduce phase(1/3 * 1/2 = 1/6) 5/6 = 1/3 + 1/3 + 1/3 * 1/2
当AP(application master)接收到a job 最后一个task执行完成的时候,它将job的status改变为 successful。然后当job拉取status时候,它自己也将会知道自己成功完成了,然后它打印信息通知给user,并从waitForCompletion()method中返回。任务的统计和计数信息此时将会输出到任务台。
最终,当job 完成的时候,AP(application master)和task容器将会清除工作状态(所以彼此通信用的输出此时会被清空)
不是那么好运,每一次执行都能正常成功,任何 一个阶段都有可能失败。下面分别从 task、application master、node manager 、resource manager出现失败来说明。
出现task failure最常见的原因就是用户的map 或 reduce程序抛出了runtime Exception。当这种情况发生的时候,task JVM将会在退出之前report error 给它的父AP(application master),这些错误信息最终将会写入到用户日志中。AP(application master)将task attempt标记为failed,同时释放容器从而让其资源可以被其他task使用。 对于streaming tasks ,如果streaming 进程以非零退出,则会被标记为failed。(由stream.non.zero.exit.is.failure属性决定,默认为true)
另一个原因就是ask JVM突然退出,有可能是JVM 的bug(额。。。我觉得这个应该是小概率事件,先考虑自己程序的问题吧。。。)
当AP(application master)有一段时间没有接收到关于task的进度更新信息时候,它将标记此task为failed,超时时间一般是10分钟。通过mapreduce.task.timeout属性设定。
对于一些应用,如果只是一小部分tasks失败了,就终止整个job是不明智的,也就是说抛开一小步分的失败不提,job中大部分的结果还是可以使用的,在这种情况下,可以设定因task失败而导致job失败的最大比值。例如,一个job由100个tasks组成,这个最大比值被设定为20%,那么如果有小于20个tasks失败了,将不会终止job,只有当失败的task超过20个时才宣布整个job失败。这一属性由mapreduce.reduce.failures.maxpercent来控制。
就像mapreduce task将会有几次重试(attempt)的机会,application in YARN也有重新启动的机会。最大的重试值由mapreduce.am.max-attempts属性决定。默认的value是2,所以当AP失败2次,它将不会从新启动,此时宣布job失败。
恢复工作的流程如下:
AP周期性地给resource manager发送心跳(heartbeats),当AP失败的时候,resource manager将会发现失败并在一个新容器中重新开启一个master的实例,而在AP中将会使用job history来恢复tasks 的state。恢复默认是允许的。如果想禁止,则将yarn.app.mapreduce.am.job.recovery.enable 置为false。
如果node manager崩溃了或者是运行的非常缓慢,它将会停止发送(或者不规律发送)heartbeats给resource manager。然后resource manager将会发现node manager不在给它发送heartbeats(10分钟不发送就能发现)。
任何运行在fail的node manager上面的task或application都将会重试(attempt)。与此同时,那些成功执行完的部分将会由application master return,因为这些在fail 的node manager上的map的中间输出可能不能被reduce任务所读取。
如果一个node manager经常发生失败,那么application master可能会将它列入“黑名单”(比如超过三个task在同一个node上失败,由mapreduce.job.maxtaskfailures.per.tracker属性控制),再次调度的时候可能将task分配到其他node上面。
(要注意的是黑名单机制只在同一个application中起作用,所以新的job中的task是会被提交到一个被旧job标记为黑名单的node上面的)
如果Resource Manager也发生了failure,那事情就比较严重了。因为没有了它不管是job还是task 容器都将不能启动。在默认情况下,resource manager是单点故障(single point of failure),因为当机器failure时,所有正在执行的job都将fail,并且无法恢复。
为了实现HA(high availability),很有必要运行一对相互对立的resource manager,其中一个是active的,另一个则作为备份,以防不时之需。如果active resource manager失败了,那么就马上切换到备份的resource manager,而且要保证对用户是透明的。
mapreduce 保证所有reduce的输入都是按照key进行过排序的。shuffle的过程就是将map操作的输入转化为reduce操作的输入的这一过程。
当map函数开始产生输出的时候,不仅仅是简单地写的硬盘上,而是做了更多的操作,充分地利用了内存缓存来进行写操作。下图来说明都做了哪些事情。
每一个map任务都有一个循环内存缓冲区用来讲output写进去。这个buffer默认的大小是100MB(由mapreduce.task.io.sort.mb属性控制)当缓存中的内容超过特定的阈值时候,一个后台线程将会将缓存内容写入到硬盘中。同时map的输出也将会继续写入到缓存中(被写到硬盘的部分将会被覆盖,这也为啥是环形的由来),但是如果说buffer不幸满了,那么map操作将会被阻塞(生产中生产数据太快,消费者还没来得及消费)。下面讲下这个写入硬盘的线程都做了什么,首先,这个线程将数据partition,根据的是它们最终将会被送给哪一个reduce进行分区。相同的reduce的数据划分到同一个分区中,同时在一个分区中执行sort by key操作。如果还有combiner function被指定的话,它将会在sort之后的数据上执行。执行combiner function 将会使最终的output更加紧凑,所以是更少的数据被写到本地硬盘并传输给reduce。(因为无论是硬盘io还是网络传输都是非常耗时的,这里也印证了对map输出进行压缩的必要性)map的output是通过http协议传输给reducers的。
8.2 The Reduce Side
由于reduce的输入来自不同的map操作,所以每当有一个map执行结束时,reduce都要讲它的输出copy到自己所在的节点,这一过程叫做reduce的copy phase。reducer会有小数目的copy线程,从而实现并行copy。(copy线程的数目由mapreduce.reduce.shuffle.parallelcopies属性决定)
那么reduce是怎么知道要去哪里拷贝它所需要的map output呢? 当一个map task 执行成功时候,它将会通知它的application master 使用的也是heartbeats机制。因此,对于一个给定的job,application master知道output和它的host的映射关系。reducer里的一个线程周期性的询问master,来获取output所在的host,知道它得到所有的数据。主机并不会在reducer取走map output的时候就马上将数据删除,因为可能过一段时间reduce操作可能失败,在重试的过程中,又需要来拷贝数据。所以,只有当application master通知host可以删除数据的时候,它才会执行删除操作,这时候job也执行完了。
当所有的map output数据都拷贝好后,reduce task 将会进入sort阶段,或者更合适地叫做merge阶段,因为sort操作已经在map端执行过了,它只是将map 的outputs进行merge,保证整体也是sort的。(这里其实和归并排序的原理类似)这个merge操作是按照回合来执行的,比如一共有50个map output是,而merge factor 是10(10是默认值,有MapReduce.task.io.sort.factor属性决定),将会有5个merge回合,每个回合合并10个文件到一个文件,最终得到五个中间文件。然后reduce并不将这五个文件合并为一个排序好的文件,merge操作saves a trip to disk by directly feeding the reduce function in what is the last phase: the reduce phase.这个最终的merge将会从内存-硬盘中混合执行。
《Hadoop: The Definitive Guide 4th edition》 page:185-208