作为新一代的资源调度统一框架,Yarn(Yet Another Resource Negotiator),在不断的完善过程中,其用途已经绝不限于Hadoop的生态圈内,业界中,流式计算如Storm,Spark均可以在一定的封装之后,运行于Yarn上,但是,就目前的发展进度来说,Yarn的基本框架,还是没有彻底的变化。
就Yarn的整体架构来说,ResourceManager作为资源调度框架的老大,集中管理集群中的资源,其手下有一堆小弟,这群小弟叫做NodeManager,听起来也很好懂,节点管理器,想想,Yarn实际上还是Master/Slave的架构,一群NodeManager,拥护着老大ResourceManager,好不热闹。
简单来说,Yarn就是分担着不同角色的几台机器组合起来,非常简单。
但是,问题来了。
1:独立的机器是不可能完成分布式框架运作的,必须完成通信,那么,ResourceManager与NodeManager是如何完成通信的?通信过程中传递的都是什么样的消息?
2:延续第一点,消息传递的格式是网络传输的二进制流毫无疑问,但是是什么样的二进制流?或者说,采用了什么样的序列化机制?这也是Yarn相对于MR1的一大改进。
3:我们平时写了一个MapReduce程序,其到底是如何提交到Yarn集群上运行起来的,这个叫做JobClient的中间商,担负了什么样的角色?
不想不知道,一想吓一跳,其中很多的圈圈绕绕,我还没有完全看得通透。
带着这些问题,一点点琢磨。
在MR1中,集群中当之无愧的老大是JobTracker,其不仅负责整个集群的资源调度管理,而且还要承担起进程管理的职责,需要监督每个应用的运行状况。
很明显,这就带来了问题。
- 因为JobTracker毕竟是单机的,如此大的消耗,肯定是吃不消的,这就是MR1的瓶颈所在。
- 扩展性不强,因为资源管理和作业控制强制绑定在一起,我们的MR1就只能运行MapReduce的程序,其他的程序,是跑步起来的,至少目前来说是这样的。
- 在设计之初,考虑到MapTask和ReduceTask在资源占用上大不相同,所以分别为Map任务和Reduce任务分配了Map的slot和Reduce的slot,看似合理,但实际上,我们程序运行的时候,会出现资源的空置情况,MapTask运行的时候,诸多的ReduceSlot处于闲置,而Reduce任务运行的时候,Map Slot又都尸位素餐,很明显这绝对不合理
或者从更深一层的角度来说,作业控制和资源调度二者,本身就不应该绑定在一起,从更合理的层面来说,资源调度管理的是集群中所有的计算资源,如CPU,IO,网络宽带等资源,不应该让他们牵涉到作业控制之中;这样分为两层,大家其乐融融,互不干扰,岂不妙哉。
这样的思想下,资源调度管理被拆分出来,做成了通用的模块,而其他程序,也可以完美地利用底层的资源框架,优哉游哉地运行自己的程序,完全不用考虑底层的事情,再好不过了。
这样做的好处在于:
- 对于大数据作业的编写人员来说,只需要考虑如何优化自己的作业即可,不用担心底层资源分配优化的问题
- 通用的资源调度和管理框架,可以合理地运行更多的作业。
在这样的思想推动下,Yarn应运而生,而且蓬勃发展起来。
盗图一张,来自于Hadoop技术内幕,董老师的书:《深入解析YARN架构设计与实现原理》,有兴趣的可以认真看下这本书。
就这章基本的架构图来说,为Yarn添加几个名词:
1:ApplicationMaster:
对于MR1来说,我们编写的MapReduce程序,都是JobClient提交给集群,然后由JobTracker负责对作业的运行情况进行监督和管理。
这就是我们上面提到的,JobTracker一边负责给提交的作业寻找资源,一边还要监督整个作业的运行状况。
而在Yarn中,我们把JobTracker的作业管理拆分出来,针对每一个作业都有一个作业管理器,叫做ApplicationMaster,对于用户来说,提交的每一个作业,都有一个AM,在2.0以后版本的Hadoop中,自带一个MRAppMaster,所以我们平时在使用MapReduce作业的时候,没有意识到这个Master的存在,但却一直在用着的。
说下ApplicationMaster的功能吧:
- 与RM调度器协商以获取资源:ResourceManager作为资源调度框架的老大,最清楚整个集群的资源目前是个什么状况,而NodeManager互相之间是没有通信的,它们统一地,定时地告诉老大自己目前的战斗力,而这个老大,负责与ApplicationMaster进行沟通,告诉客户(ApplicationMaster)自己的资源,两个人琢磨琢磨,能不能给出足够的资源;所以,ApplicationMaster启动之后的第一件事情,就是先去拿资源;这里有个问题,启动之前呢?或者说ApplicationMaster是怎么启动的(潜藏问题1)?
- 与NM通信以启动/停止任务:来自于董老师书中的说法,我觉得很好,怎么表示自己占用了这么多的资源呢?毫无疑问,那就是把任务启动起来,自然就占用了对应的资源,而如果任务执行完毕了,那就释放获取到的资源,在某些情况下,ApplicationMaster也可以自行释放自己占用的资源。
- 监控所有的任务运行状态,并且在任务运行失败时,重新为任务申请资源以重启任务:这个不予赘述了。
- 将得到的任务进一步分配给内部的任务(后文再说)。
大家能看到,这里面一直提到了资源,这是个有点虚的词汇,而在Yarn中,有一个词来描述,叫做Container。
2:Container
作为Yarn中的资源抽象,其描述了某个节点上的多维度资源,包括内存,CPU,磁盘,网络等,当AM向RM申请资源时候,RM为AM返回的资源就是用Container来表示的,Yarn会为每个任务分配一个Container,该任务只能使用该Container中描述的资源。这里必须注意,Container和Slot是不同的,是一个动态资源划分的单位。
其实,这部分更应该从源码角度来分析比较合适:源码在org.apache.hadoop.yarn.api.records中:
简单说下:
@Private
@Unstable
public static Container newInstance(ContainerId containerId, NodeId nodeId,
String nodeHttpAddress, Resource resource, Priority priority,
Token containerToken) {
Container container = Records.newRecord(Container.class);
container.setId(containerId);
container.setNodeId(nodeId);
container.setNodeHttpAddress(nodeHttpAddress);
container.setResource(resource);
container.setPriority(priority);
container.setContainerToken(containerToken);
return container;
}
代码来自于Hadoop-2.6.5版本,Container的初始化,就是用该方法实现的,可以看到Container的几个主要成员变量:id是Container在整个集群中唯一的标识;NodeId是Container所在的节点标识,通过这个值,Application可以联系到对应的NM,拉取到RM分配给自己的资源;其他不多说,大家可以参见代码,注释非常详细。
这里说下其中的Resource:
@Public
@Stable
public static Resource newInstance(int memory, int vCores) {
Resource resource = Records.newRecord(Resource.class);
resource.setMemory(memory);
resource.setVirtualCores(vCores);
return resource;
}
就2.6.5版本来说,只支持内存和虚拟核数的分配,就是内存资源和CPU资源的分配,这里可以看出来,Container是一个完全动态化的概念,也可以看出来其跟Map Slot和Reduce Slot的区别。
其实这里还存在一个问题,我们提交作业时候指定的资源参数,是否会传到这里?如果提交的时候,没有指定资源量,那默认是怎么分配Container的?
本文先不介绍这些更细致的东西,后续再说。
本文写作时候,Hadoop已经到了3.1.0的版本,可以指定更多的资源:
public static final String MEMORY_URI = "memory-mb";
public static final String VCORES_URI = "vcores";
public static final String GPU_URI = "yarn.io/gpu";
public static final String FPGA_URI = "yarn.io/fpga";
已经定义的资源是上面四大类,其他未曾定义的资源,可以自行定义在相应的Resource-type.xml文件中。
3:JobClient
谈到这里,还是有一个概念隐藏在各处,但是并未详述,这就是JobClient。
无论是在HDFS中,还是在Yarn中,都存在着一个Client的概念,或者更进一步地说,我们平时提交的作业,操作的HDFS Shell脚本,并非是真正与ResourceManager或者NameNode打交道,而是与Client打交道,这个Client把我们的命令,真正提交给后端的服务。
就Yarn来说,其中已经有了指定的MRAppMaster,在我们提交MapReduce作业的时候,实际上提交的是MRAppMaster,然后由JobClient提交给了RM,当RM知道有了一个新的作业,会估量资源是否足够,然后首先分配资源,先把ApplicationMaster启动起来,等到MRApplicationMaster启动之后,才是我们本文讨论的后续细节(这里回答了上文的潜藏问题1)。
就本文而言,更偏向于静态的概念描述,文中提到的一些未解决的问题,倾向于动态的运行过程,我们提交的一个作业,究竟是如何在Yarn的资源调度框架下完整运行起来,运行过程中报出的那些异常,到底都是哪些类的问题,解决这些问题,更有助于我们对于Yarn的深刻理解。