大数据处理系统:Hadoop源代码情景分析
上QQ阅读APP看书,第一时间看更新

3.3 Hadoop的YARN框架

如前所述,用户眼中的Hadoop更多的是其YARN子系统,因为HDFS只起存储的作用, YARN才进行数据的处理和计算。所以,当用户要在Hadoop上执行某个“作业(Job)”时,首先要将其提交给YARN的主节点ResoueceManager,尽管这个用户很可能是在某个从节点的终端上。从节点只能执行主节点下达的任务,而不能自作主张。

用户在某个从节点将作业提交到主节点后,下面就是主节点如何筹划、调度和指派执行的事了。这里面一个突出的问题就是资源管理,因为怎么调度、把作业指派到哪些节点并监督管理其运行,说到底是个如何分配系统资源的问题。

在早期的Hadoop系统,即Hadoop 2.0版以前的系统中,还没有ResoueceManager和NodeManager,在Hadoop集群的主节点上扮演着“中央政府”角色的是JobTracker,即JobTracker类的对象,也是个独立的Java虚拟机进程。与此相对,每个从节点上都有个TaskTracker,也都是独立的JVM进程。当然,JobTracker与TaskTracker之间是管理与被管理的关系,所以整个集群在逻辑上是一种单层的主/从式星形结构。JobTracker把用户提交的作业分解成许多“任务(Task)”,例如一个Mapper就是一个Task,一个Reducer也是一个Task,并根据作业的要求和各节点的资源情况将这些Task指派给某些节点,然后由这些节点上的TaskTracker将这些Task投入运行。当然,主节点也好,从节点也好,将自始至终跟踪管理这些Task的运行,只是层次不同,主节点是在作业即Job层次上跟踪管理,从节点是在任务即Task层次上跟踪管理。Tracker这个词就是因此而来的。

但是,从2.0版开始,Hadoop引入和实现了一种新的、称为YARN的资源管理与调度机制,形成了相应的YARN框架(YARN Framework)。从Hadoop 2.0版开始,虽然代码中还留有一些残迹,JobTracker和TaskTracker却已经不复存在,分别被ResoueceManager和NodeManager所取代。

YARN是“Yet Another Resource Negotiator”的缩写,意为与JobTracker/TaskTracker不同的另一种资源协商管理机制。实际上以YARN取代JobTracker/TaskTracker的努力从Hadoop的0.23版就开始了,但是这意味着对Hadoop代码进行伤筋动骨的改写,所以主流的Hadoop版本一时仍维持原来的JobTracker/TaskTracker框架不变,直到2.0版才正式改用YARN。

那么采用了YARN以后的计算机集群是怎样管理计算资源并调度作业运行的呢?后面有好几章讲的都是YARN各方面的细节,但是这里先对YARN做一扼要的概述还是很有必要的。

在YARN这个框架中,作为一个独立JVM进程,在主节点上扮演着相当于“中央政府”角色的是“资源管理者(RM)”,一个ResourceManager类对象。在每个从节点(普通节点)上扮演着“地方政府”角色的则是“节点管理者(NM)”,一个NodeManager类对象,也是作为一个独立的JVM进程而存在的。

主节点上的资源管理者RM掌管着集群内各个节点上资源的分配使用,记录着各节点的资源使用情况。这里的所谓资源就是存储器和CPU。存储器是指虚存空间,CPU也是指“虚核”,即VCore。什么叫VCore呢?一个进程,一个线程,一台Java虚拟机,都可以占用一个VCore,这要看具体的需要和安排,但是一个线程绝对不需要两个虚核。反过来,多个虚核也可以并发、分时地落实到同一个实体的CPU核上,但此时系统的性能就很可能有所下降。所以,一个节点的VCore容量某种程度上决定了允许启动JVM的数量。

一个作业被提交到RM,其申请材料首先会被保存起来,排入一个等待调度的队列。然后RM通过一个YARN调度模块进行调度,首先在它的“账本”中选出一个具有足够空闲资源的节点,让其担任相当于“项目组长”的角色,并将一个操作系统层面的Shell命令行连同所分配的资源配额等信息打包作为一个“容器(Container)”发送给这个节点,使其就地启动一个进程作为这个作业的“应用主管”,即Application Master,缩写成AM。这就好比,“中央”在收到作业请求后就为其“立项”,并指定一个“地方政府”作为该项目的“牵头单位”,在当地成立一个项目组,组长的角色就称为AM,就好像企业中把项目经理(Proj ect Manager)称为PM一样。

注意,这里说的是“Application Master”,而不是“Job Master”。与从前的JobTracker不同,现在主节点上的RM已经把视野从“作业”扩展到了“应用”,意思是可以在YARN框架中运行的不再限于原来那样的Java作业,也可以是普通的任何“应用”。后面我们将看到,在YARN框架中运行的也可以不是Java类,还可以不是MapReduce。

此外,交由“牵头单位”用来启动AM进程的shell命令行也并非固定不变,而是取决于所提交的具体应用。对于常规的、作为Java类的MapReduce作业,这个命令行是“~/bin/java… MRAppMaster …”,所以MapReduce作业的“项目组长”AM就是MRAppMaster,意为“MapReduce的App Master”。

如上所述,这个AM是作为一个独立的进程、独立的Java虚拟机运行的,是一个“独立法人”,而不是当地NodeManager内部的一个模块,与NodeManager不在同一个JVM上。

一个应用(作业)一旦有了AM,这个“项目组长”便要负起对于这个作业的责任。首先要分析这个作业的种种情况(在一个称为Context的数据结构中),看需要把它分解成几个任务,有几个任务就需要几个容器,即打成几个包。假定一个作业要求有16个Mapper和一个Reducer,那么至少就需要17个容器(不算AM本身所占的容器),或者说需要打成17个包,这样才能把它们分发到不多于17个的节点上去运行,这就好像包工头把一个项目拆散了分包出去一样。注意,“容器(Container)”并非物理的概念,实际上只是对用来完成具体任务的软件(一般是Java类)及其所分配资源和所指派节点的描述,但是到了所指派的节点上就会落实为一个独立的进程。如果是Java程序,那就是一个独立的JVM。至于其所需的资源,则如前所述就是内存空间和虚拟核VCore的数量。

容器是要向RM申请的,并非AM可以自行发放,因为RM统管着全局的资源。所以, AM需要就具体应用所含的任务逐一向RM申请容器。而RM则通过其调度员(Scheduler)加以调度,根据当时的资源情况和具体任务的需求分配资源并将此任务指派到某个节点,这样就形成了一个容器。不过RM并不直接将容器发送给所指派的节点,而只是发回给AM。一级管一级的事,RM只管到这一级,下面就是AM的事了。

当然,调度的时候可以有各种不同的考虑和策略,所以Hadoop提供了三个不同的Scheduler,以插件的形式供选择,你想用哪一种就插上哪一种。这三种Scheduler是FifoScheduler、FairScheduler和CapacityScheduler。其中FifoScheduler是最简单的,它的调度策略是FIFO,先来先得,只要手里的资源还够,就给。当然,同一个容器中的资源,即同一个任务所需的资源,必须是在同一个节点上。FairScheduler就要复杂一些,其调度策略是Fair,要考虑作业之间,实际上是用户之间的公平合理。CapacityScheduler就更麻烦,还要考虑怎样使用资源才可在总体上臻于最优。如果不另行规定,那么默认的就是最复杂的CapacityScheduler。

AM得到了RM分配下来的容器之后,就要与该容器所指派的节点联系,将容器转发给这个节点,以“发动(launch)”这个容器所包含的任务在目标节点上运行。而受指派的节点,在拿到一个容器之后,则先要把容器中指定的数据文件(或片断)和程序映像“本地化(localize)”,将它们复制到本地,然后就启动这个任务,作为一个进程运行。

为什么要把容器中的任务作为进程运行,(对于Java类)为什么要为其启动一个独立的JVM,而不是放在NodeManager所在的JVM上作为一个模块(“类”)运行呢?这里有几个原因。首先是对安全的考虑。假定这个任务是一个Mapper,这一般是由用户提供的,程序质量得不到保证,里面万一有个什么Bug就可能把整个NodeManager拖垮。而若是作为一个独立的进程运行就不怕,到时候大不了把这进程杀(kill)了。其次还有灵活性的考虑。在新采用的YARN框架中,具体任务所要执行的也不一定就是Java程序,也可能是个C/C++的应用,甚至还可以是个脚本,那就难以纳入NodeManager。再说,在一个节点上可能需要运行分属不同应用的多个任务,例如可能有来自应用A的两份Mapper和应用B的一份Mapper加一份Reducer,如果不把它们作为独立的进程运行,就会给管理带来困难。

发动了容器所含的任务在所指派节点上运行之后,AM和RM并非就此袖手旁观,而是通过“心跳(Heartbeat)”密切注视和管理着这些任务的运行。假定AM发现自己在某个节点上的任务出了问题(例如超时没有心跳),并且事先已安排了备用容器(称为Speculator),它就可能会下达命令,把出问题的进程kill掉,并让备用容器(通常是在另一个节点上)顶上去。否则,如果事先没有安排备用容器,它也可以临时向RM另外申请一个容器,以这个新分配的容器替换已经出了问题的容器。此外,用户常常要查询作业的进展情况,但是用户并不知道自己这个作业的AM在哪里,也无法直接与AM交互,因此所有的查询都要流经RM。

这样的方案,这样的机制和框架,应该说是很合理的,读者不妨想想是否还有更合理的方案。读者也许会问,为什么要把AM放到别的节点上,而不是留在主节点上,甚至就作为RM的内部模块加以执行?其实早期的JobTracker正是这样的。但是想想:要是集群的规模达到了几千台机器,甚至上万台机器,那么同时在集群中运行的应用数量,也即AM的数量就可能很大,这对于主节点显然是个不小的负担。另一方面,如果把AM都集中在主节点上,就会把本可以分散的许多网络流量都集中到中央而造成拥堵。最后,把AM都集中在一个节点上,也使单点故障的后果变得更加突出而难以恢复。

当然,这么一来对于作业的管理就复杂起来了,系统的开销也因此而增加了不少。然而事情往往就是这样,一种计算模式,如果只是小规模应用,固然可以做得很简洁很高效,而如果想要对付大规模应用,就免不了一定程度的复杂性上升和效率下降。