前言
理想情况下,YARN应用发出的资源请求应该立即给予满足。然而现实中资源时有限的,在一个繁忙的集群上,一个应用经常需要等待才能得到所需的资源。YARN调度器的工作就是根据既定的策略为应用分配资源。调度通常是一个难题,并且没有一个所谓”最好”的策略,这也是为什么YARN提供了多种调度器和可配置策略供我们选择的原因。
调度策略
YARN中有三种调度器可用:FIFO调度器(FIFO Scheduler),容量调度器(Capacity Scheduler)和公平调度器(Fair Scheduler)。Apache版本的默认配置是容量调度器,CDH版本的默认配置是公平调度器。
FIFO Scheduler
FIFO调度器将应用放置在一个队列中,然后按照提交的顺序(先进先出)运行应用。首先为队列中第一个应用的请求分配资源,第一个应用的请求被满足后再依次为队列中下一个应用服务。
FIFO调度器的优点是简单易懂,不需要任何配置,但是不适合共享集群。大的应用会占用集群中的所有资源,所以每个应用必须等待直到轮到自己运行。在一个共享集群中,更适合使用容量调度器或公平调度器。这两种调度器都允许长时间运行的作业能及时完成,同时也允许正在进行较小临时查询的用户能够在合理时间内得到返回结果。
下面是FIFO调度器的调度模式。
在使用FIFO调度器时,小的作业一直被阻塞,因为大的作业先提交,而且将集群资源占满了。直到大作业运行完成,小作业才能够获取到资源开始执行。
Capacity Scheduler
容量调度器也很容易理解,YARN会创建一个专门的队列保证小的作业一提交就可以启动,由于队列容量是为那个队列中的作业所保留的,因此这种策略是以整个集群的利用率为代价的。这就意味着与FIFO调度器相比,大作业的执行时间要长。
从图中可以看出,当提交第一个大任务的时候,该任务将队列中的资源全部占满,但是另外一个小队列(queue B)没有受到影响,因此第二个小任务提交时就能够立即获取到资源并开始运行。
Fair Scheduler
公平调度器目前使用的最为广泛,它不需要预留一定量的资源,因为调度器会在所有运行的作业之间动态平衡资源。
第一个大作业启动时,它也是唯一运行的作业,因而获得集群中所有的资源。当第二个小作业启动时,它被分配到集群的一半资源,这样每个作业都能公平共享资源。注意,从第二个作业的启动到获得公平共享资源之间会有时间滞后,因为它必须等待第一个作业使用的容器用完并释放出资源。当小作业结束且不再申请资源后,大作业将再次使用全部的集群资源。最终的结果是:既得到了较高的集群利用率,又能保证小作业能及时完成。
调度器配置
下面我们主要讨论容量调度器和公平调度器的配置选项。
容量调度器配置
容量调度器允许多个组织共享一个Hadoop集群,每个组织可以分片到全部集群资源的一部分。每个组织被配置一个专门的队列,每个队列被配置为可以使用一定的集群资源。队列可以进一步按层次划分,这样每个组织内的不同用户能够共享该组织队列所分配的资源。在一个队列内,使用FIFO调度策略对应用进行调度。
单个作业使用的资源不会超过其队列容量。然而,如果队列中有多个作业,并且队列资源不够用了呢?这时如果仍有可用的空闲资源,那么容量调度器可能会将空余的资源分配给队列中的作业,哪怕这会超出队列容量。这称为”弹性队列”(queue elasticity)。
正常操作时,容量调度器不会通过强行中止来抢占容器。因此,如果一个队列一开始资源够用,然后随着需求增长,资源开始不够用时,那么这个队列只能等着其他队列释放容器资源。缓解这种情况的方法时,为队列设置一个最大容量限制,这样这个队列就不会过多侵占其他队列的容量了。当然,这样做是以牺牲队列弹性为代价的,因此需要在不断尝试和失败中找到一个合理的折中。
假设一个队列的层次结构如下。
1 | root |
下面是基于上述队列层次的容量调度器配置文件,文件名为capacity-scheduler.xml
。在root
队列下定义两个队列:prod
和dev
,分别占用40%和60%的容量。需要注意的是,对特定队列进行配置时,是通过以下形式的配置属性yarn.scheduler.capacity.<queue-path>.<sub-property>
进行设置的,其中,root.prod
。
1 |
将应用放置在哪个队列中,取决于应用本身。例如,在MapReduce中,可以通过设置属性mapreduce.job.queuename
来指定要用的队列。如果队列不存在,则在提交时会发生错误。如果不指定队列,那么应用将被放在一个名为default
的默认队列中。
注意,对于容量调度器,队列名应该是队列层次名的最后一部分,完整的队列层次名是不会被识别的。例如,对于上述配置,prd
和eng
是合法的队列名,但root.dev.eng
和dev.eng
作为队列名则是无效的。
公平调度器配置
公平调度器旨在为所有运行的应用公平分配资源。在前面介绍公平调度器时展示了同一个队列中的应用是如何实现资源公平共享的。然而公平共享实际也可以在多个队列间工作。
假如两个用户A和B,分别拥有自己的队列。A启动一个作业,在B没有需求时A会分片到全部可用资源;当A的作业仍在运行时B启动一个作业,一段时间后,按照我们先前看到的方式,每个作业都用到了一半的集群资源。这时,如果B启动第二个作业并且其他作业仍然在运行中,那么第二个作业将和B的第一个作业共享资源,因此B的每个作业将占用四分之一的集群资源,而A仍继续占用一半的集群资源。最终的结果就是资源在用户之间实现了公平共享。
启用公平调度器
Apache版本的Hadoop默认使用容量调度器,如果要使用公平调度器,需要将yarn-site.xml
文件中的yarn.resourcemanager.scheduler.class
设置为公平调度器的完全限定名:org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler
。
队列配置
通过一个名为fair-scheduler.xml
的文件对公平调度器进行配置,该文件位于类路径下。当没有该文件时,公平调度器的工作策略如同先前所描述的一样:每个应用放置在一个以用户名命名的队列中,队列是在用户提交第一个应用时动态创建的。
通过该文件可以为每个队列进行配置。例如定义队列、为队列设置权重等。
1 |
需要注意的是,当设置权重时,记住要考虑默认队列和动态创建的队列。虽然没有在文件中为它们指定权重,但它们仍有值为1的权重。
在上面的配置中,我们希望每个生产性作业能够顺序运行且在最短可能的时间内结束,所以prod队列使用了FIFO调度策略。值得注意的是,在prod和dev之间、eng和science队列之间及内部划分资源仍然使用了公平调度。
另外,每个队列仍可配置最大和最小资源数量,及最大可运行应用的数量。最小资源数量不是一个硬性的限制,但是调度器常用它对资源分配进行优先排序。如果两个队列的资源都低于它们的公平共享额度,那么远低于最小资源数量的那个队列有限被分配资源。最小资源数量也会用于接下来介绍的抢占行为。
抢占
在一个繁忙的集群中,当作业被提交给一个空队列时,作业不会立即启动,直到集群上已经运行的作业释放了资源。为了使作业从提交到执行所需的时间可预测,公平调度器支持”抢占”(preemption)功能。
所谓抢占,就是允许调度器终止那些占用资源超过了其公平共享份额的队列的容器,这些容器资源释放后可以分片给资源数量低于应得份额的队列。注意,抢占会降低整个集群的效率,因为被终止的containers需要重新执行。
通过将yarn.scheduler.fair.preemtion
设置为true,可以全面启用抢占功能。有两个相关的抢占超时设置:一个用于最小共享,另一个用于公平共享,两者设定时间均为秒级。默认情况下,如果超时参数均不设置。所以为了允许抢占容器,需要至少设置其中一个超时参数。
如果队列在最小共享超时指定的时间内未获得被承诺的最小共享资源,调度器就会抢占其他容器。可以通过配置文件的顶层元素defaultMinSharePreemptionTimeout
为所有队列设置默认的超时时间,还可以通过设置每个队列的minSharePreemptionTimeout
元素来为单个队列指定超时时间。
类似,如果队列在公平共享超时指定的时间内获得的资源仍然低于其公平共享份额的一半,那么调度器就会抢占其他容器。可以通过配置文件中的顶层元素defaultFairSharePreemptionTimeout
为所有队列设置默认的超时时间,还可以通过设置每个队列的fairSharePreemptionTimeout
元素来为单个队列指定超时时间。通过设置defaultFairSharePreemptionThreshold
和fairSharePreemptionThreshold
可以修改超时阈值,默认值时0.5。
延迟调度
所有的YARN调度器都试图以本地请求为重。在一个繁忙的集群上,如果一个应用请求某个节点,那么极有可能此时有其他容器正在该节点上运行。显而易见的处理是,立刻放宽本地行需求,在同一机架中分配一个容器。然而,通过实践发现,此时如果等待一小段时间(不超过几秒),能够戏剧性的增加在所请求的节点上分配到一个容器的机会,从而可以提高集群的效率。这个特性称之为延迟调度
。容量调度器和公平调度器都支持延迟调度。
YARN中的每个NodeNamager周期性的(默认每秒一次)向ResourceManager发送心跳请求。心跳中携带了NodeManager中正运行的容器、新容器可用的资源等信息,这样对于一个计划运行一个容器的应用而言,每个心跳就是一个潜在的调度机会。
当使用延迟调度时,调度器不会简单的使用它收到的第一个调度机会,而是等待设定的最大数目的调度机会发生,然后才放松本地性限制并接收下一个调度机会。
对于容量调度器,可以通过设置yarn.scheduler.capacity.node-locality-delay
来配置延迟调度。设置为正整数,表示调度器在放松节点限制、改为匹配同一机架上的其他节点前,准备错过的调度机会的数量。
公平调度器也使用调度机会的数量来决定延迟时间,尽管是使用集群规模的比例来表示这个值。例如将yarn.scheduler.fair.locality.threshold.node
设置为0.5,表示调度器在接受同一机架中的其他节点之间,将一直等待直到集群中的一半节点都已经给过调度机会。还有个相关的属性yarn.scheduler.fair.locality.threshold.rack
,表示在接受另一个机架替代所申请的机架之前需要等待的时长阈值。
小结
本章介绍了YARN所支持的几种调度器,分别是FIFO Scheduler、Capacity Scheduler以及Fair Scheduler。其中容量调度器是Apache版本的Hadoop默认配置的调度器,公平调度器则是CDH版本的Hadoop默认配置的调度器。每种调度器都有其优点,应当根据实际情况选择不同的调度器。不过在实际场景中,公平调度器使用的较多,其次是容量调度器。公平调度器和容量调度器都支持延迟调度策略,尽可能的使得任务在数据本地节点运行,简单来说就是在真正分配NodeManager创建容器时,等待一定时间,希望能在数据所在的节点启动容器。