Hadoop-Yarn学习
1 基本概念
Yarn全名Yet Another Resource Negotiator
,即资源协调/管理者,在Hadoop2中引入。
1.1 Yarn是什么
Yarn,英文全名是 Yet Another Resource Negotiator,是由雅虎开发的第二代集群资源调度器。查看论文点这里。Yarn在大数据体系中的示意图如下:
而应用层在Application层之上,如Hive等。
和第一代不同,Yarn对各个角色进行了重新抽象。Yarn把JobTracker划分为了管理集群资源的ResourceManager(以下简称RM)和管理集群上运行任务的生命周期的AppMaster(以下简称AM)。此外,还有一个负责管理上报所在节点资源、响应处理AM的任务启停请求的角色NodeManager(以下简称NM)。
- 基本的思路
AM向RM申请资源(Container),RM调度分配Container后,App拆分后的task在container上运行。NM监控该节点的Container,确保App使用的资源不会超过配额。
1.2 Yarn的架构
如上图所示,Yarn的架构里包括以下角色:
1.2.1 Client客户端
负责向Yarn提交App作业
1.2.2 Resource Manager-资源管理器
每个Yarn集群一个RM(还可以设一个standBy节点做HA),负责整个集群的计算资源的管理和分配,RM主要由以下两部分组成
- 调度器(Scheduler)
调度器根据容量、队列等限制条件,将系统中的资源(container)分配给各个正在运行的App;不负责具体应用程序相关的工作,比如监控或跟踪状态(AM负责);不负责重新启动失败任务(AM负责)。调度器是一个可拔插的组件,YARN提供了多种直接可用的调度器,比如Fair Scheduler和Capacity Scheduler等,用户还可以编写符合规范的自定义调度器。 - 应用程序管理器(ASM)
管理提交到RM的所有App。体现到代码里主要是org.apache.hadoop.yarn.server.resourcemanager.RMAppManager
1.2.3 Node Manager-节点管理器
每个Yarn集群中有多个NM,每个NM有多个Container用于Container的启动和监测(每个NM上可能有多个Container)
注:根据Yarn配置的不同,Container可能是一个Unix进程或者一个Linux cgroup实例,在受限的资源范围内(如内存、CPU等)执行特定应用程序的代码。
NM主要功能如下:
- 启动和监视节点上的计算容器(Container)
- 以心跳的形式向RM汇报本节点上的资源使用情况和各个Container的运行状态
- 接收并处理来自AM的Container启动/停止等各种请求
1.2.4 App Master-应用程序管理器
协调、管理App的所有task。AM和task都运行在container中, container资源由RM调度分配, 由NM管理
AM 主要功能如下:
- 为App向RM申请所需Container资源
- 将得到的资源进一步分配给内部的任务
- 与NM通信以启动/停止Container容器(请求带有这些信息:资源配额、安全性令牌(如果已启用)、启动Container的命令、进程环境、必要的二进制文件/ jar 等)、获取Container状态信息
- 监控App作业的所有task运行状态,并在task运行失败时重新为其申请资源以重跑
1.2.5 Container-资源的抽象
- Container是资源的抽象,他表示资源分配单位(CPU,内存等)。Container是一个动态资源分配单位,可以是UNIX进程或Linux cgroup,具体决定于Yarn配置(),它内部封装了内存、CPU、磁盘、网络等资源,用cgroup等方法限定每个task的资源量。
- Container由RM调度, 由NM管理,AM可以根据需要动态申请和释放Container。
- Container可运行任意程序不限于Java
注意:Container不同于MRv1中的slot,它是一个动态资源划分单位,是根据应用程序的需求动态生成的。
1.3 YARN资源隔离方案
YARN对内存资源和CPU资源采用了不同的资源隔离方案:
- 对于内存资源
为了能够更灵活的控制内存使用量,默认情况YARN采用了进程监控的方案控制内存使用,即每个NodeManager会启动一个额外监控线程监控每个container内存资源使用量,一旦发现超过指定资源,则会将该子进程杀死。
采用这种机制的另一个原因是Java中创建子进程采用了fork()+exec()的方案,子进程启动瞬间,它使用的内存量与父进程一致,从外面看来,一个进程使用内存量可能瞬间翻倍,然后又降下来,采用线程监控的方法可防止这种情况下导致swap操作以及误杀。而Cgroup对内存监控缺乏灵活,一旦发现任务使用内存资源超过阈值就会立刻杀死或抛出OOM异常。 - 对于CPU资源
采用了Cgroups进行资源隔离。
CGroups是一种将任务集及其所有未来子级集合/划分为具有特殊行为的分层组的机制。它是Linux内核功能,已合并到Linux 内核版本2.6.24中。 从YARN的角度来看,可使容器的CPU资源使用受到限制。
1.4 pull-based异步资源分配
注意, YARN的资源分配过程是异步的,即RM将资源分配给一个application后,它不会立刻push给对应的AM,而是暂存到缓冲区,等待AM通过周期性的RPC函数主动来取。也就是说,资源分配采用pull-based模型,而不是push-based模型。
2 Yarn作业执行原理
2.1 概述
- Client提交一个应用到RM,申请第一个Container来运行AM
- RM找到合适的NM,NM在Container容器内启动AM
- AM将资源申请的请求放在随NM发往RM的心跳中
- RM通知NM启动Container来运行应用程序
注意,需要注意的是,用户应用程序间各个组件的通信必须自己实现,Yarn没有提供通信工具。
2.2 作业本地性要求
在AM为tasks向RM申请Container时,该请求包括每个map task的数据本地化信息,特别是输入split所在的节点和机架(rack)信息。调度器会使用这些信息来进行调度决策。调度器会优先将task分配到所需数据所在节点执行,这就是所谓数据本地化策略,避免了远程访问数据的各种开销。
如果不能分配,那就会根据用户设置的RelaxLocality(本地化松弛,默认为true)来将task分配到本地化节点同机架的其他节点上。
比如申请处理HDFS数据的Container时,先考虑拥有该数据block副本的节点,如果没有再考虑这些副本所在节点同机架的其他节点,如果还是没有只能申请任意节点。
2.3 申请资源方式
- 在最开始就申请所有需要的资源
典型的就是Spark,申请固定的vcores, 内存,启动固定数量的Exector - 根据变化的需求来动态申请资源
典型的是MapReduce,最开始申请map task容器,此时还没有申请reduce task容器。 - 失败恢复
当有task失败时,会申请额外的容器来重跑失败的task。
2.4 MR On Yarn
2.4.1 MR作业运行流程
先说明下,每次提交Application的作业就是job,一般包括多个任务(task)。一个MapReduce job 包括多个map task和 reduce task。
我们这里引用《Hadoop权威指南》中一张十分经典的的MR Job运行在Yarn中的流程图:
下面说下详细步骤:
- 客户端进程启动job
- 向RM发出请求,获取一个代表此job的全局唯一appID
- Client 检查job的输出说明,计算输入split分片数,并将job资源(包括job的jar、Configuration、分片信息等)写入到HDFS内,以便后续执行task时读取。这一步很关键。
- Client向ResourceManager提交job。此提交请求中包含一个ApplicationSubmissionContext,他包括了RM为该App启动AM的所有信息:
- App ID
- App User
- App Name
- App 优先级
- ContainerLaunchContext,他包括NM启动Container所需信息,包括 containerId、Container索要的资源量、user、安全token、启动container的本地资源依赖(如binary jar等)、运行环境变量、运行命令等
- maxAppAttempts AM尝试的最大重试次数
- attemptFailuresValidityInterval 故障计数时间间隔
- 该Job的AppMaster启动,分为两个步骤:
5a. RM首先为AppMaster在某个NM上分配、启动一个Container
5b. NM收到RM命令, 使用分配的Container来启动AppMaster - AM的主类MRAppMaster做一些Job的初始化工作,如监控作业进度等。
- 通过HDFS得到由客户端计算好的输入split,然后为每个输入split创建一个map task, 再根据
mapreduce.job.reduces
创建指定数量的reducer task.
然后AM决定如何运行构成整个job的tasks。如果job很小, AM根据用户配置可以选择在本节点的JVM中运行该job, 这种job称作是uber job。 - AM为tasks向RM申请Container
如果该job不是uber类型,那么AM机会向RM请求container来运行所有的map和reduce任务。 (注:每个任务对应一个container,且只能在该container上运行)。
该请求是一个由描述资源需求的ResourceRequest组成的列表,随后RM返回分配的资源描述Container。ResourceRequest的Protocol Buffers定义如下:
message ResourceRequestProto {
optional PriorityProto priority = 1; // 资源优先级
optional string resource_name = 2; // 资源名称(资源本地化信息,期望资源所在的node_host、rack名称等)
optional ResourceProto capability = 3; // 资源量(仅支持CPU和内存两种资源)
optional int32 num_containers = 4; // 满足以上条件的资源个数
optional bool relax_locality = 5 [default = true]; //是否支持本地性松弛
}
该请求包括每个map task的数据本地化信息,特别是输入split所在的节点和机架(rack)信息。调度器会使用这些信息来进行调度决策。调度器会优先将task分配到所需数据所在节点执行,这就是所谓数据本地化策略,避免了远程访问数据的各种开销。如果不能分配,那就会根据用户设置的RelaxLocality(本地化松弛,默认为true)来将task分配到本地化节点同机架的其他节点上。
请求还包括了task的内存需求, 默认情况下map和reduce任务的内存需求都是1024MB。 可以通过mapreduce.map.memory.mb
和mapreduce.reduce.memory.mb
配置。
- Hadoop2中分配内存的方式和Hadoop1中不一样, Hadoop1中每个tasktracker有固定数量的slot, slot是在集群配置是设置的, 每个任务运行在一个slot中, 每个slot都有最大内存限制, 这也是整个集群固定配置的。这种方式很不灵活。可能的问题如小内存需求任务占用slot导致内存利用率低或是大内存需求任务占有slot但无足够内存导致失败。
- 而在YARN中, 资源划分的粒度更细。App的内存需求可以介于最小内存和最大内存之间, 但必须是最小内存的整数倍,分配更灵活。
注意,AM向RM发出自愿申请后不会立刻受到满足要求的Container资源,所以AM需要不断尝试与RM通信来拉取。RM可成功分配Container时,会返回Container描述信息给AM,每个这样的Container描述可用于启动一个任务。Container描述Protocol Buffers定义如下:
message ContainerProto {
optional ContainerIdProto id = 1; // Container id
optional NodeIdProto nodeId = 2; // Container所在NM id
optional string node_http_address = 3; // Container所在NM地址
optional ResourceProto resource = 4; // Container资源量
optional PriorityProto priority = 5; // Container优先级
optional hadoop.common.TokenProto container_token = 6; //Container用于安全认证的token
}
- AM受到若干Container描述后,将他们分派给内部task。在Container资源成功分配给一个task后,分两个步骤来启动该task:
a. AM向NM发送启动指定container的RPC请求StartContainersRequest,其中封装了ContainerLaunchContext和Container信息,和一个任务对应;
b. NM通过收到的请求上下文中的信息带参数执行YarnChild
类的main方法。
第8、9步骤如下图: - 资源本地化-去HDFS拉取task所需资源
在运行task之前,先去HDFS拉取task需要的所有文件到本地, 比如作业配置、JAR文件等所有来自HDFS的文件(具体需求在第九步提交的StartContainersRequest中),这一步称为数据资源本地化。 - 启动Map或者ReduceTask
注意:YarnChild运行在一个专用的JVM中, 但是YARN默认不支持JVM重用,因此每一个任务都是运行在一个新的JVM中。但是可以用uber job配置 - 应用程序运行完成后,AM向RM注销并关闭自己。
2.4.2 MRJob进度和状态更新
YARN中的task将其执行进度和状态上报给AM(使用TaskUmbilicalProtocol协议), AM每隔三秒将所有task上报的状态信息合并为该Job的整体状态视图。
如果开启了verbose,Client每秒(默认值为1秒,可以通过mapreduce.client.progressmonitor.pollinterval
设置)向AM请求查询任务实时的执行情况,方便用户查看。
在YARN中, 可以通过UI看到所有的App运行情况。具体来说,RM的WebUI 展示运行中的App以及对应的AM。AM展示管理的tasks进度等细节信息。
2.4.3 MRJob的完成
在Job生命周期内,Client可从AM查询Job进度, 还会每5秒(未开启verbose时的默认值,可通过mapreduce.client.completion.pollinterval
设置)检查是否完成。
Job完成之后, AM和container会清理自己的工作状态, OutputCommiter的作业清理方法也会被调用。Job的信息会被Job历史服务器存储以备之后用户核查。
2.4.4 MRJob失败处理
Yarn的失败包括task失败、AM失败、NM失败甚至是RM失败,下面详细介绍下这几种情况。
2.4.4.1 task运行失败
task程序运行时异常和JVM进程突然退出会上报到AM,此次task尝试失败。
还有一种情况是task挂起。task运行过程中会定期调用TaskUmbilicalProtocol
协议中ping方法联系AM,如果程序挂起导致AM一直收不到ping(通过mapreduce.task.timeout
设置),就会判定该task超时,AM将该次task尝试标记为失败。如果将上述配置设为0 ,代表task不会被标记为失败,导致其资源无法释放,可能会出大问题,不推荐。
task尝试失败后,AM尝试重新执行该任务,同时要避免在失败节点上重跑。重试次数的配置如下:
map task:mapreduce.map.maxattempts
,
reduce task:mapreduce.reduce.maxattempts
。
task尝试失败次数超过这个阈值,就会认为这个task失败,不再进行重试。
最后说下怎么判定整个job的失败。如果一个MapReduce job中超过mapreduce.map.failures.maxpercent
的map task 或者mapreduce.reduce.failures.maxpercent
reduce task运行失败,就判定该job失败。
2.4.4.2 AM失败
AM会定时向RM发送心跳。如果AM故障(如硬件故障或网络错误等),RM可感知到并在新的container中运行一个新的AM实例。在默认情况下,只要AM运行失败一次,然后重试一次又失败就被认为是AM失败了不再重试。可以通过mapreduce.am.max-attempts
来设置该值。
Yarn有一个全局参数yarn.resourcemanager.am.max-attempts
,默认值为2,可以控制所在yarn上运行的AM的重试次数,也就是说上面讲的每个任务的重试次数不能超过这个全局参数。
MapReduce的AM恢复后,可以通过job历史来恢复故障App所运行tasks的状态,使其不需要重跑。可以通过设置yarn.app.mapreduce.am.job.recovery.enable
为false,关闭此功能。
Client会向AM轮询job进度报告,当AM运行失败时,客户端需要重新定位新的AM实例。在job初始化的时候,client就通过请求RM得到并缓存了AM地址,这样做的好处是Client每次向AM查询时变得更快。在AM运行失败时,Client会因为请求超时而重新向RM请求最新的AM地址,这个过程对用户完全透明。
2.4.4.3 NM失败
NM也会定期向RM发送心跳。如果RM在10分钟内(默认值,可通过yarn.resourcemanager.nm.liveness-monitor.expiry-interval-ms
设置)发现未收到任何来自某个NM节点的心跳,RM会将该节点从节点池中移除并通知该节点。
在该失败的NM上运行的所有App或AM就按照上文所述机制恢复。
注意:由于map task输出结果驻留在失败NM上的原因,所以在这些节点上运行成功的map task(所属job还未完成)还是需要重新调度运行,以免造成NM节点被踢掉后reduce任务无法拉取所需数据
最后要注意的是,如果累积的App运行失败次数过多,那么所在NM节点可能会被AM放入自己管理的黑名单(不管NM是否失败过)。对于MapReduce任务,默认累积超过3个task失败就会将此NM拉黑,任务会尽量分配到其他节点。该阈值的配置是mapreduce.job.maxtaskfailures.per.tracker
。
注意:当前版本hadoop中,黑名单由应用程序的AM管理,对RM透明,也就是说就算老的App把某个NM节点拉黑,但是新的App的任务依然有可能分配到这些故障节点
2.4.4.4 RM失败
RM作为Yarn的大脑,如果失败后果十分严重,如果没有配置StandBy RM的话会存在单点故障风险,会导致所有运行的job全部失败且无法恢复。可以用共享存储解决这个问题,源码中该接口为RMStateStore
,利用ZooKeeper(ZKRMStateStore)或HDFS(FileSystemRMStateStore)做了RM HA那么active RM挂掉的情况下会自动切换到standBy RM,Client无明显感知。
RM HA时可以把运行中的App(不包括AM管理的任务信息)信息存储在高可用的Zookeeper或HDFS中备份,以备failover时备节点恢复关键状态信息。NM的信息未保存,因为是心跳调度机制,当备RM节点恢复服务收到NM心跳时可以快速重构NM状态信息。
RM备节点接管后,会从上面的状态存储区中读取信息进行恢复,为所有App重启AM。
上述RM HA过程是由FailoverController
自动处理,他在默认情况下运行在RM内,使用zookeeper的leader选举机制来确保同一时刻只有一个主RM。
2.5 UberJob
2.5.1 JVM重用
首先,简单回顾一下Hadoop 1.x中的JVM重用功能:用户可以通过更改配置,来指定TaskTracker在同一个JVM里面最多可以累积执行的Task的数量(默认是1)。这样的好处是减少JVM启动、退出的次数,从而达到提高任务执行效率的目的。 配置的方法也很简单:通过设置mapred-site.xml里面参数mapred.job.reuse.jvm.num.tasks的值。该值默认是1,意味着TaskTracker会为每一个Map任务或Reduce任务都启动一个JVM,当任务执行完后再退出该JVM。依次类推,如果该值设置为3,TaskTracker则会在同一个JVM里面最多依次执行3个Task,然后才会退出该JVM。
在 Yarn(Hadoop MapReduce v2)里面,不再有参数mapred.job.reuse.jvm.num.tasks,但它也有类似JVM Reuse的功能——uber。据Arun的说法,启用该功能能够让一些任务的执行效率提高2到3倍(“we’ve observed 2x-3x speedup for some jobs”)。不过,由于Yarn的结构已经大不同于MapReduce v1中JobTracker/TaskTracker的结构,因此uber的原理和配置都和之前的JVM重用机制大不相同。
2.5.2 uber的原理
Yarn的默认配置会禁用uber组件,即不允许JVM重用。我们先看看在这种情况下,Yarn是如何执行一个MapReduce job的。首先,Resource Manager里的Application Manager会为每一个application(比如一个用户提交的MapReduce Job)在NodeManager里面申请一个container,然后在该container里面启动一个Application Master。container在Yarn中是分配资源的容器(内存、cpu、硬盘等),它启动时便会相应启动一个JVM。此时,Application Master便陆续为application包含的每一个task(一个Map task或Reduce task)向Resource Manager申请一个container。等每得到一个container后,便要求该container所属的NodeManager将此container启动,然后就在这个container里面执行相应的task。等这个task执行完后,这个container便会被NodeManager收回,而container所拥有的JVM也相应地被退出。在这种情况下,可以看出每一个JVM仅会执行一Task, JVM并未被重用。
用户可以通过启用uber组件来允许JVM重用——即在同一个container里面依次执行多个task。在yarn-site.xml文件中,改变一下几个参数的配置即可启用uber的方法:
参数 | 默认值 | 描述 |
mapreduce.job.ubertask.enable | (false) | 是否启用user功能。如果启用了该功能,则会将一个“小的application”的所有子task在同一个JVM里面执行,达到JVM重用的目的。这个JVM便是负责该application的ApplicationMaster所用的JVM(运行在其container里)。那具体什么样的application算是“小的application"呢?下面几个参数便是用来定义何谓一个“小的application" |
mapreduce.job.ubertask.maxmaps | 9 | map任务数的阀值,如果一个application包含的map数小于该值的定义,那么该application就会被认为是一个小的application |
mapreduce.job.ubertask.maxreduces | 1 | reduce任务数的阀值,如果一个application包含的reduce数小于该值的定义,那么该application就会被认为是一个小的application。不过目前Yarn不支持该值大于1的情况“CURRENTLY THE CODE CANNOT SUPPORT MORE THAN ONE REDUCE” |
mapreduce.job.ubertask.maxbytes | application的输入大小的阀值。默认为dfs.block.size的值。当实际的输入大小部超过该值的设定,便会认为该application为一个小的application。 |
最后,我们来看当uber功能被启用的时候,Yarn是如何执行一个application的:
- Resource Manager里的Application Manager会为每一个application在NodeManager里面申请一个container,然后在该container里面启动一个Application Master。
- containe启动时便会相应启动一个JVM。
- 此时,如果uber功能被启用,并且该application被认为是一个“小的application”,那么Application Master便会将该application包含的每一个task依次在这个container里的JVM里顺序执行,直到所有task被执行完(“WIth ‘uber’ mode enabled, you’ll run everything within the container of the AM itself”)。这样Application Master便不用再为每一个task向Resource Manager去申请一个单独的container,最终达到了 JVM重用(资源重用)的目的。
3 Yarn调度
3.1 基本概念
3.1.1 概述
随着大量应用基于Yarn申请资源运行,所以资源调度是重中之重,我们需要根据实际情况选择调度策略,目前有三类调度器,如下图
3.1.2 心跳调度
默认情况下,每个NM以1秒的时间间隔周期性的发送心跳给RM,心跳信息携带了NM的运行中container以及其他可用资源信息。所以,每次心跳就是一次潜在的调度资源运行container的机会。
3.1.3 延时调度
3.1.3.1 背景
在繁忙集群中,如果一个应用请求特定节点资源,则可能有很多其他container已经在该节点上运行无空闲资源。此时最简单的方式就是放松本地性而在同机架的其他空闲节点上。
3.1.3.2 概念
然而,有统计在实践中如果再等很短一段时间(一般不超过几秒)就很有机会能分配请求的本地性节点的资源从而提高集群整体效率。这个延迟调度的过程就称为延时调度机制。
开启延迟调度后,调度器就可在放松本地性限制前先等待最大调度机会次数,以此尽可能使用本地性调度。
3.1.3.3 Capacity调度器中的延时调度
将yarn.scheduler.capacity.node-locality-delay
设为正数,默认为一个机架上的节点数即40。经典做法是将其设为集群中的NM节点数。
3.1.3.4 Fair调度器中的延时调度
参见Fair Scheduler章节配置小结的yarn.scheduler.fair.locality.threshold.node
和yarn.scheduler.fair.locality.threshold.rack
。
3.1.4 DRF-占优资源公平机制
3.1.4.1 概念
全称Dominant Resource Fairness
。
只有一种资源如内存时,资源分配的公平性很好界定,但要用多个维度时就不好衡量,因为不同维度资源量无法统一量化。
比如一个集群100个CPU和10TB内存。应用A请求(2CPU,300GB内存),占整个集群的百分比为(2%,3%),则百分比更大的内存为占优资源;B请求(6CPU,100GB内存),占整个集群的百分比为(6%,1%),占优资源为CPU。所以公平共享条件下,B应该分配2倍于A的资源(因为6%=3% * 2
)
3.1.4.2 Capacity调度器中的DRF
Capacity调度器的默认yarn.scheduler.capacity.resource-calculator
为DefaultResourceCalculator
就是只计算内存,而可选DominantResourceCalculator
来使用DRF机制计算多个维度的资源如内存、CPU等。
3.1.4.3 Fair调度器中的DRF
可设定defaultQueueSchedulingPolicyi
或schedulingPolicy
为drf。
3.2 FIFO
- 使用场景
适用于独立集群或调试,一般不会在线上共享集群使用 - 原理
FIFO调度器将所有提交的应用放置在一个队列,然后按提交顺序先进先出方向来分配资源,待先提交的应用资源满足后运行,再依次服务后续应用。如果还有剩余足够资源则可分配给后续应用。 - 特点
简单无需配置,但无法跳开大资源应用其他必须等待(大型应用可能占据所有资源)。FIFO策略中小作业必须阻塞等待大作业完成。
3.3 Capacity Scheduler-容量调度器
3.3.1 基本概念
3.3.1.1 概述
- 使用场景
适用于大量业务团队共享的集群。 - 原理
一个集群配置多个队列且队列内可进一步划分队列形成队列树形结构,每个队列被配置使用一定量的集群资源,队列内部使用FIFO方式调度。
多个业务团队使用一个或多个队列,同一个团队的不同用户就可以共享分配的队列资源。 - 队列弹性调度
单个作业使用的资源不会超过其所在队列资源容量。但如果作业没有足够资源且集群其他队列有空闲资源可用时,容量调度器可能触发队列弹性机制来分配空闲资源给该作业,此时可能该队列使用资源超过设置的队列阈值。
这样能保证队列资源弹性可用,作业运行时间可预测,可阻止人为分配队列造成的浪费,提高集群整体资源利用率。 - 抢占
默认容量调度器不会强行终止来抢占container资源(除非设置work-preserving
抢占,此时RM会要求应用返还多占用的资源来平衡队列容量),也就是说资源不够时只能等待其他队列释放容器资源,此时可设置队列最大容量。 - 特点
小作业提交到专门的队列快速开始,不受别的队列的大作业影响。但降低了集群整体资源利用率,大作业执行时间较FIFO更长。其他特点:
- 层次队列
- 队列容量保证
- 支持ACL安全控制
- 弹性调度
- 多租户
提供了全面的限制措施,以防止单个应用程序、用户或队列垄断队列或整个群集的资源 - 动态配置
可在运行时动态修改容量、ACL、增加队列等 - 队列可清空
管理员可停止某队列,该队列正在运行的不受影响,而不能再提交应用到该队列。管理员也可重启已停止的丢列。 - 支持DRF
默认只计算内存 - 可映射Unix user或Unix group到queue
- 可支持应用优先级调度
仅适用于队列内的FIFO调度情况
3.3.1.2 应用优先级
队列内的FIFO调度可设置优先级,整数值越高则应用优先级越大。
3.3.1.3 抢占
可使得调度器从那些使用的资源量超过了配置的保证容量的队列处抢占container资源。
3.3.1.4 Reservation
CapacityScheduler支持ReservationSystem
,它允许用户提前保留资源。
应用程序可以在提交时通过指定reserveId
来在运行时请求所保留的资源。
3.3.2 配置
请参考:
3.3.2.1 yarn-site.xml
- yarn.resourcemanager.scheduler.class
请设置为org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler
,表示RM使用容量调度器。
3.3.2.2 capacity-scheduler.xml
yarn.scheduler.capacity.<queue-path>.<sub-property>
基本配置格式,queue-path
表示.
分隔的队列层次路径,sub-property
表示属性名。- yarn.scheduler.capacity.root.chengc.capacity
某个队列基础容量百分比 - yarn.scheduler.capacity.root.chengc.maximum-capacity
某个队列最大容量百分比,可使得队列不过多占用集群资源。
设置太小可能牺牲集群队列弹性,设置太大会使得队列占用过多资源导致其他队列无资源可用,需要反复尝试调整。 - yarn.scheduler.capacity.root.chengc.user-limit-factor
允许单个用户最多可获取的队列资源的倍数。
默认值为1,为浮点数,可确保单个用户永远不会占用超过队列配置的资源无论集群是否空闲。
注意:
- 这列不是精确值,可能实际使用的资源量会稍微超过一些。
- 该比例不能超过设置的该队列最大资源量(如
chengc
队列的yarn.scheduler.capacity.root.chengc.maximum-capacity
),就算设置超过也按队列最大资源量获取资源上限,只能超过yarn.scheduler.capacity.root.chengc.capacity
- 可参考Capacity Scheduler 中 user-limit-factor 参数的理解
3.3.3 配置示例
假设有以下队列:
root
|--prod
|--dev
|--eng
|--science
则capacity-scheduler.xml
文件配置示例如下:
<?xml version="1.0"?>
<configuration>
<property>
<name>yarn.scheduler.capacity.root.queues</name>
<value>prod,dev</value>
</property>
<property>
<name>yarn.scheduler.capacity.root.dev.queues</name>
<value>eng,science</value>
</property>
<property>
<name>yarn.scheduler.capacity.r oot.prod.capacity</name>
<value>40</value>
</property>
<property>
<name>yarn.scheduler.capacity.root.dev.capacity</name>
<value>60</value>
</property>
<property>
<name>yarn.scheduler.capacity.root.dev.maximum-capacity</name>
<value>75</value>
</property>
<property>
<name>yarn.scheduler.capacity.root.dev.eng.capacity</name>
<value>50</value>
</property>
<property>
<name>yarn.scheduler.capacity.root.dev.science.capacity</name>
<value>50</value>
</property>
</configuration>
该配置要点如下:
- 定义了root的两个直接队列子队列prod(40%)和dev(60%)
- dev之下又分别定义了子队列eng和science分别50%
- dev队列设置最大容量百分比75,换句话说prod总是至少有25%资源可用
- 其他队列都没有设置maximum-capacity,则eng和science的作业都可能使用dev所有资源(上限为整个集群75%);而prod的作业甚至可能使用整个集群所有资源。
更多配置如单个用户、单个应用分配最大资源、并发运行应用数、队列ACL认证等内容请参考:
3.3.4 队列使用
使用容量调度器时,要指定作业运行队列,名字请使用层级队列末尾名字,比如要使用root.dev.eng
这个队列必须时而用eng
而不能使用全名。不指定队列名时浆放入default队列。
3.4 Fair Scheduler-公平调度器
3.4.1 概念
3.4.1.1 概述
- 使用场景
追求集群整体资源利用率时。 - 原理
公平调度器为所有应用公平分配资源,公平性体现在队列间和队列中:
- 比如上图最开始只有队列A的Job1在运行于是获得集群所有资源;
- 队列B的Job2提交后,RM告知Job1返还集群一半Container资源。待资源返还后Job2获得一半资源开始运行;
- Job3提交后,待Job2腾退1半资源后开始拥有队列B的一半资源运行;
- Job2运行完毕后,释放资源,随后Job3获得该队列所有资源高效运行。
- 队列内部可选fifo、fair、drf三种调度,默认fair。
- 特点
集群整体资源利用率高,大作业能加快速度完成,小作业也能及时完成。
不需要预留资源,因为调度器会在所有运行的作业之间动态平衡分配资源。
具体来说,首个运行的大作业可获得集群所有资源;第二个小作业启动时可等待第一个大作业使用的部分容器用完释放后分配集群一半资源,保证了公平性。且第二个小作业结束后释放了资源,大作业又可以再次使用全部集群资源了。
3.4.1.2 队列选择匹配机制
由allocations文件内的queuePlacementPolicy
定义rule
元素列表依次匹配来决定提交的应用放在哪个queue中。
3.4.1.3 Preemption抢占机制
- 背景
在一个繁忙的、空闲资源很少或没有的集群中,新提交一个job到空队列时不会立刻启动,而必须等待其他队列job释放资源且调度器分配足够资源给本队列后才行。
所以为了让job从提交到启动时间可预测,fair调取器支持抢占策略。 - 原理
抢占机制规定调度器可以kill那些超过公平共享份额的队列所运行的container,这样就能将释放出的资源分配给那些资源低于公平共享份额的队列。
抢占时机:
- 一个队列等待了
minSharePreemptionTimeout
(默认最顶层defaultMinSharePreemptionTimeout
)还未收到至少达到minResource的资源,则调度器可能会抢占其他队列运行的container。 - 一个队列等待了
fairSharePreemptionTimeout
(默认最顶层defaultFairSharePreemptionTimeout
)还未收到至少达到fairSharePreemptionThreshold * fairShare
(这里的阈值默认也是顶层的defaultFairSharePreemptionThreshold
)的资源的资源,则调度器可能会抢占其他队列运行的container。
- 缺点
因为container需要先杀死再重启,所以抢占机制会降低集群整体效率。 - 配置
参考后续3.4.2配置章节preemption相关内容。注意默认没有抢占超时时间,所以想启用抢占还必须设定minSharePreemptionTimeout
和fairSharePreemptionTimeout
中至少一个。
3.4.2 配置
请参考:
3.4.2.1 yarn-site.xml
- yarn.resourcemanager.scheduler.class
请设置为org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler
,表示RM使用公平调度器分配资源。 - yarn.scheduler.fair.allocation.file
队列配置文件名称,默认为fair-scheduler.xml
。该文件主要用于描述各个队列的属性,比如资源量、权重等
如果没有该文件,则每个应用提交时会被放置到一个以用户命名的队列中,这些队列是提交该应用的用户首次提交应用时动态创建的。 - yarn.scheduler.fair.user-as-default-queue
默认true
当应用程序提交时未指定队列名时,是否使用用户名作为默认队列名。如果设置为false或者未设置时所有未知队列的应用程序将被提交到default
队列。 - yarn.scheduler.fair.preemption
默认false
是否启用抢占机制 - yarn.scheduler.fair.preemption.cluster-utilization-threshold
开启了抢占后生效,整体集群资源利用率阈值 - yarn.scheduler.fair.sizebasedweight
默认false
默认在队列内部分配资源时采用公平轮询的方法,将资源分配给各个应用,不考虑资源大小。
设置为true时,应用程序将以1的自然对数加上应用程序的总请求内存除以2的自然对数来加权。即按照应用程序资源请求量来分配资源,即需求资源数量越多,权重越高。 - yarn.scheduler.assignmultiple
默认fale
是否允许一次心跳多次分配资源。 - yarn.scheduler.fair.dynamic.max.assign
默认true
开启assignmultiple
时,是否开启动态决定一次心跳调度时分配资源量。开启后,该心跳NM节点的约一半未分配资源会在单次心跳中被分配给container们。 - yarn.scheduler.fair.max.assign
默认-1代表不限制
开启assignmultiple
且禁用dynamic.max.assign
时生效,用来指定一次心跳调度中分配的最大container数目。 - yarn.scheduler.fair.locality.threshold.node
默认-1.0,表示不跳过任何调度机会。该值表示为介于0到1之间的浮点数。
对于在特定NM节点上请求容器的应用程序,自上一次容器分配以来在接受另一个NM节点上的放置之前要等待的调度机会次数阈值。
当按照分配策略,可将一个节点上的资源分配给某个应用程序时,如果该节点不是应用程序期望的节点,可选择跳过该分配机会暂时将资源分配给其他应用程序,
直到出现满足该应用程序需的节点资源出现。通常而言,一次心跳代表一次调度机会,而该参数则表示跳过调度机会占节点总数的比例。 - yarn.scheduler.fair.locality.threshold.rack
默认-1.0,表示不跳过任何调度机会。该值表示为介于0到1之间的浮点数。
当应用程序请求某个特定机架上的资源时,它可接受的可跳过最大资源调度机会次数阈值。 - yarn.scheduler.fair.allow-undeclared-pools
默认true,即可在应用提交时创建新队列(由于应用提交时指定的队列或者取决于user-as-default-queue
属性)。
如果为false,只要应用提交的队列未提前指定,则总是会提交到default
队列中。
如果allocations配置文件中定义了队列规则,则该属性将会被忽略。 - yarn.scheduler.fair.update-interval-ms
默认500ms
锁定调度器以重新计算公平份额、重新计算资源需求以及检查是否触发抢占的时间间隔。
3.4.2.2 fair-scheduler.xml
3.4.2.2.1 队列配置
- minResources
一个queue的最小资源量,设置格式为X mb, Y vcores
,队列资源小于公平份额的会在同parent队列级别被优先分配资源;如果多个队列同时不满足minResources,则资源优先分配给相对资源使用量/最小共享资源
的比例最小的队列。minResources也被用在抢占(preemption)机制。
对于不同的调度策略,minResources含义不同:
- fair策略只考虑内存资源。即如果一个队列使用的内存资源使用率低于最小内存共享份额,则认为此时不满足;
- 对于drf策略,则考虑主资源使用量占整个集群资源比例是否小于占优资源的最小共享份额。
- maxResources
一个queue最多可以使用的资源量,可写为X mb, Y vcores
或X% memory, Y% cpu
(表示占整个集群资源百分比)。
如果队列申请的container资源加上该队列已使用的资源超过了maxResources,则不会分配该container。 - maxChildResources
一个ad hoc子queue最多可以使用的资源量,可写为X mb, Y vcores
或X% memory, Y% cpu
(表示占整个集群资源百分比)。
如果队列申请的container资源加上该ad hoc子queue已使用的资源超过了maxResources,则不会分配该container。 - maxRunningApps
限制一个队列同时运行的应用程序数。 - maxAMShare
浮点数,默认0.5f,可填-0.1f禁用此检查。
限制一个队列的公平份额中可用来运行AM的部分,只对叶子队列生效。比如0.9f会允许叶子队列使用内存和CPU资源的90%的。 - weight
默认为1。
队列间的分配资源权重值,越大分配的越多,比如设为2的队列资源是默认的队列资源的大约2倍。 - schedulingPolicy
默认fair
本队列采用的调度模式,内嵌可选的有fifo、fair、drf,也可是继承自org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.SchedulingPolicy
的类。 - aclSubmitApps
可向队列中提交应用程序的用户或用户组列表,默认情况下为“*”,表示任何用户均可以向该队列提交应用程序。
需要注意的是,该属性具有继承性,即子队列的列表会继承父队列的列表。配置该属性时,用户之间或用户组之间用``分割,
用户和用户组之间用空格分割,比如user1, user2 group1,group2
。 - aclAdministerApps
该队列的管理员账户/管理员组列表。
一个队列的管理员可管理该队列中的资源和应用程序,比如可杀死任意应用程序。 - minSharePreemptionTimeout
默认Long.MAX_VALUE,即表示从不抢占资源。单位为秒。
如果一个队列在该段时间内使用的资源量低于最小共享资源量,则开始抢占其他队列的资源。
如果不设置,就从父队列继承该值。 - fairSharePreemptionTimeout
默认Long.MAX_VALUE,即表示从不抢占资源。单位为秒。
如果一个队列在该段时间内使用的资源量低于fair共享资源量,则开始抢占其他队列的资源。
如果不设置,就从父队列继承该值。 - fairSharePreemptionThreshold
默认0.5f,该队列的公平共享资源抢占阈值
如果队列等到了fairSharePreemptionTimeout
都还没有收到fairSharePreemptionThreshold * fairShare
的资源,就允许从其他队列抢占container以获取资源。
如果不设置,就从父队列继承该值。 - allowPreemptionFrom
默认true。
决定调度器是否能从该队列抢占资源。
如果设定为false,则会递归到所有子队列。 - reservation
向ReservationSystem表明该队列的资源可对用户提供保留。
该配置仅适用于叶子队列。 如果未配置此属性,则叶子队列不能提供保留。
3.4.2.2.2 其他顶层配置
以下设置在allocations
下一级。
- User
单个用户的设置,目前可为单个用户添加maxRunningApps
属性限制其最多同时运行的应用程序数量。 - userMaxAppsDefault
没配置maxRunningApps的默认用户同时运行的应用程序数量。 - defaultFairSharePreemptionTimeout
队列的fairSharePreemptionTimeout属性的默认值。 - defaultMinSharePreemptionTimeout
队列的minSharePreemptionTimeout属性的默认值。 - defaultFairSharePreemptionThreshold
队列的fairSharePreemptionThreshold属性的默认值。 - queueMaxAppsDefault
队列的maxRunningApps属性的默认值。 - queueMaxResourcesDefault
队列的maxResources属性的默认值。 - queueMaxAMShareDefault
队列的maxAMShare属性的默认值。 - defaultQueueSchedulingPolicy
队列的schedulingPolicy属性的默认值。 - reservation-agent
默认值org.apache.hadoop.yarn.server.resourcemanager.reservation.planning.AlignedPlannerWithGreedy
。
该项应设定实现自ReservationAgent
的类,用来放置用户的驻留请求到Plan
中。 - reservation-policy
默认值org.apache.hadoop.yarn.server.resourcemanager.reservation.CapacityOverTimePolicy
。
该项应设定实现自SharingPolicy
的类,用来验证新的驻留请求是否违反任何规则。 - reservation-planner
默认值org.apache.hadoop.yarn.server.resourcemanager.reservation.planning.SimpleCapacityReplanner
。
该项应设定实现自Planner
的类,会在Plan
容量跌落(由于维持调度或节点故障)到低于用户保留资源时调用。此时它会扫描Plan
并按接受的逆序(LIFO)贪心地删除保留,直到保留的资源在Plan
容量之内。 - queuePlacementPolicy
包含若干rule
元素用来告知调度器把新提交的应用放入哪个队列,其中rule的命中以列表中配置先后顺序为准。
rule元素的create
参数的含义是决定是否本规则可创建新队列,默认为true
;如果设置为false,且该规则命中后试图把应用放入没在allocations
问价那种配置的队列,则会跳过本rule继续匹配队列中的下一条rule。注意,最后一条规则必须配置不能再跳过。
未配置queuePlacementPolicy
时,默认就是如下配置,此时就是先按用户指定的队列提交,若无指定或不存在就使用用户名队列:
<queuePlacementPolicy>
<rule name="specified"/>
<rule name="user"/>
</queuePlacementPolicy>
合法的rule值如下:
- specified
将应用放入所请求的队列中。如果该应用未指定请求任何队列(即它指定了default队列)或指定的队列不存在,则我们继续匹配下一条rule。如果该应用请求的队列名称以句点开头或结尾,即类似.q1
或q1.
”将被拒绝。 - user
应用程序以提交用户的名称放入队列中。用户名中的句点将替换为_dot_
,即用户first.last
的队列名称为first_dot_last
。 - primaryGroup
将应用程序放入提交用户所属primary Unix group
名称的队列中。组名称中的句点将替换为_dot_
。 - secondaryGroupExistingQueue
将应用程序放入第一个与提交用户匹配的secondary Unix group
名称的队列中。组名中的句点将被替换为_dot_
。 - nestedUserQueue
将应用放置在由嵌套rule决定的队列中。这类似于user
规则,区别在于nestedUserQueue
规则中,用户队列可以在任何父队列下创建,而user
规则仅在root
队列下创建用户队列。请注意,只有在嵌套规则返回父队列时才应用nestedUserQueue规则。可以通过将队列的type
属性设置为parent
或通过在该队列下配置至少一个叶子队列来配置父队列。 - default
应用程序放置在default规则的queue
属性指定的队列中。如果未指定queue
属性,则将应用程序放置在root.default
队列中。 - reject
该应用被拒绝。
3.4.3 配置示例
3.4.3.1 简单示例
同样假设有以下队列:
root
|--prod
|--dev
|--eng
|--science
则可配置如下
<?xml version="1.0"?>
<allocations>
<defaultQueueSchedulingPolicy>fair</defaultQueueSchedulingPolicy>
<queue name="prod">
<weight>40</weight>
<schedulingPolicy>fifo</schedulingPolicy>
</queue>
<queue name="dev">
<weight>60</weight>
<queue name="eng"/>
<queue name="science"/>
</queue>
<queuePlacementPolicy>
<rule name="specified" create="false"/>
<rule name="primaryGroup" create="false"/>
<rule name="default" queue="dev.eng"/>
</queuePlacementPolicy>
</allocations>
该配置要点如下:
- 定义了root的两个直接队列子队列prod(权重40%)和dev(权重60%)。注意权重可以不设定为百分比,这里只是为了简单,也可以设为2和3。
- 权重被用在公平调度计算时,比如这个例子中资源分配40%给prod,60%给dev就认为符合公平规则。以用户名命名队列虽没指定但权重为1。
- dev之下又分别定义了子队列eng和science,因没有指定weight所以默认为平均分配
- 最顶层的
defaultQueueSchedulingPolicy
可以定义每个队列内部的默认调度策略。如果没有设定,则采用fair公平调度器。还可选fifo和drf(Dominant Resource Fairness) - 对于每个队列,可单独设定
schedulingPolicy
。
比如上述prod队列采用了fifo调度器 - queuePlacementPolicy配置了3个队列匹配rule
- 第一行代表应用放入指定的队列且不能创建不存在队列(此时就跳过本rule继续匹配下一个rule);
- 第二行表示将应用程序放入提交用户所属
primary Unix group
名称的队列中且不能创建不存在队列; - 第三行意思是将所有应用程序放置在default规则的
queue
属性指定的队列即dev.eng
中。
3.4.3.2 复杂示例
例子来自于FairScheduler:
<?xml version="1.0"?>
<allocations>
<queue name="sample_queue">
<minResources>10000 mb,0vcores</minResources>
<maxResources>90000 mb,0vcores</maxResources>
<maxRunningApps>50</maxRunningApps>
<maxAMShare>0.1</maxAMShare>
<weight>2.0</weight>
<schedulingPolicy>fair</schedulingPolicy>
<queue name="sample_sub_queue">
<aclSubmitApps>charlie</aclSubmitApps>
<minResources>5000 mb,0vcores</minResources>
</queue>
<queue name="sample_reservable_queue">
<reservation></reservation>
</queue>
</queue>
<queueMaxAMShareDefault>0.5</queueMaxAMShareDefault>
<queueMaxResourcesDefault>40000 mb,0vcores</queueMaxResourcesDefault>
<!-- Queue 'secondary_group_queue' is a parent queue and may have
user queues under it -->
<queue name="secondary_group_queue" type="parent">
<weight>3.0</weight>
<maxChildResources>4096 mb,4vcores</maxChildResources>
</queue>
<user name="sample_user">
<maxRunningApps>30</maxRunningApps>
</user>
<userMaxAppsDefault>5</userMaxAppsDefault>
<queuePlacementPolicy>
<rule name="specified" />
<rule name="primaryGroup" create="false" />
<rule name="nestedUserQueue">
<rule name="secondaryGroupExistingQueue" create="false" />
</rule>
<rule name="default" queue="sample_queue"/>
</queuePlacementPolicy>
</allocations>