YARN容量调度设置资源抢占方案实践

需求

队列:

root
  -default
  -a
    -a1
    -a2
  -b
    -b1
    -b2

让 a1、a2 队列能够抢占,b1、b2 队列不抢占。

实现方案

设置调度模式为容量调度

首先需要开启容量调度,在 yarn-site.xml 中配置

<property>
    <name>yarn.resourcemanager.scheduler.class</name>
    <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</value>
</property>

开启配置后,根据规划增加子队列配置。

图片

a:a1:a2b:b1:b2default
yarn.scheduler.capacity..capacity40%:50%:50%40%:50%:50%20%
yarn.scheduler.capacity..maximum-capacity40%:100%:100%40%:100%:100%100%
yarn.scheduler.capacity..user-limit-factor222

PS:由于需要进行资源抢占,因此,需要设置最大的容量,本例中设置的是 100%,即最大可以使用父队列的 100%资源。同时还需要设置用户限制阈值,默认情况下,单用户只能使用队列的全部资源,如果要超量使用,需要配置阈值。

具体的配置细节,在文章末尾。

开启抢占

默认情况下,队列间是不进行抢占的,如果需要开启队列间资源抢占,同时,抢占策略需要单独设置。需要配置:

<property>
    <name>yarn.resourcemanager.scheduler.monitor.enable</name>
    <value>true</value>
</property>
<property>
    <name>yarn.resourcemanager.scheduler.monitor.policies</name>
    <value>org.apache.hadoop.yarn.server.resourcemanager.monitor.capacity.ProportionalCapacityPreemptionPolicy</value>
</property>

该参数是一个全局配置,开启该参数后,默认情况下是队列间都会进行抢占,如果要禁止队列抢占,可以使用如下配置(queue_path 为全路径:root.a.a1):

<!-- 队列禁止抢占 -->
<property>
    <name>yarn.scheduler.capacity.$QUEUE_PATH.disable_preemption</name>
    <value>true</value>
</property>
<!-- 禁止队列内抢占 -->
<property>
    <name>yarn.scheduler.capacity.$QUEUE_PATH.intra-queue-preemption.disable_preemption</name>
    <value>true</value>
</property>

验证

开启队列抢占后,打开 webui 开始验证。a1、a2 队列的 Preemption 状态是 enabled 状态。图片

b1、b2 队列的 Preemption 状态是 disabled 状态。

图片

提交几个任务验证。

a1、a2 开启抢占,可以看到,a1 本来运行的 container 为 4 个,a2 把资源抢走了,此时 a1 只有 2 个 container。

图片

b1、b2 未开启抢占,提交到 b2 的任务运行后,b1 已经运行的任务资源未被抢走。

图片

补充知识

队列内抢占

除了可以配置队列间的抢占,还可以配置队列内的任务是否可以抢占。也就是说不仅可以抢占其他队列的资源,还可以抢占任务自身所在队列的资源。

队列内的抢占有两种方式:一种是按任务的优先级来,即高优先级的任务处于待分配状态后,将从低优先级任务抢占资源;另一种是按用户资源来划分,即队列内多个用户提交的任务,从占用资源最多的那个用户进行抢占,其本质上是保证每个用户的资源使用趋于平等。这里就举例介绍下按任务优先级来抢占。

和队列间的抢占类似,也需要在 yarn-site.xml 中增加如下配置项才能启用队列内的抢占。

<property>
    <name>yarn.resourcemanager.monitor.capacity.preemption.infra-queue-preemption.enabled</name>
    <value>true</value>
</property>
<property>
    <name>yarn.resourcemanager.monitor.capacity.preemption.intra-queue-preemption.preemption-order-policy</name>
    <value>priority_first</value>
</property>

配置完成后(同样需重启 rm),就可以看到对应属性从 disable 变成 enabled 了。

接下来仍旧是进行测试看看实际效果,测试方法和队列间的抢占基本一致,不过有如下两点不同:

  • 最后一次任务提交仍旧是提交到 queue_test 队列中(队列间的抢占是提交到 default 队列)
  • 每个任务提交时都指定了优先级,最后一个提交的任务的优先级比之前的都要高

抢占原理

首先,只有使用的调度器实现了 PreemptableResourceScheduler 接口,并且启用了抢占;rm 才会真正使用抢占这个功能。

而资源抢占是通过第三方策略触发的,这些策略通常被实现成一些可插拔式的组件类(实现指定 SchedulingEditPolicy 接口)。yarn 提供了默认的实现类,当然,也可以通过参数配置进行指定。

rm 会启动一个监测线程,在该线程中定期遍历这些策略,并调用具体实例的接口实现方法,决定是否进行抢占,抢占哪些 container 的资源。

资源抢占的整个过程可以概括为如下步骤:

  1. 监测线程根据队列当前已使用资源大小、实际配置使用资源大小、是否允许抢占等因素,重新计算出每个队列最终分配的资源大小,需要抢占的资源大小,以及哪些 container 的资源将被抢占。然后将需要抢占的资源通过以事件机制的方式通知 rm。
  2. rm 处理对应事件,并标记待抢占的 container。
    rm 收到任务 ApplicationMaster(简称 AM)的心跳信息后,通过心跳应答将待释放的资源总量和待抢占 container 列表返回给 AM。AM 收到心跳后,可选择如下操作方式:

a. 杀死这些 container

b. 杀死其他 container 以凑够待释放资源的总量

c. 不做任何处理,因为可能有其他 container 结束自行释放资源,或者由 rm 选择杀死 container

  1. 监测线程对应定时器到期后,发现 AM 未按照指定列表 kill 待抢占的 container,则将发送包含这些 container 列表的 kill 事件给 rm。
  2. rm 收到消息后标注这些 container 将要被 kill。并在 NodeManager 的心跳应答中,告知 NodeManager 需要将指定的 container 进行 kill,以释放资源。
    这就是整个资源抢占的处理逻辑,个人认为,最核心的步骤在于资源分配的重新计算和标注哪些 container 的资源将要被抢占。

其他配置项

除了上面队列内抢占、队列间抢占提到的配置外,还有如下相关配置项:

<!-- 设置为观察模式,true 表示不进行真正的抢占动作 -->
<property>
    <name>yarn.resourcemanager.monitor.capacity.preemption.observe_only</name>
    <value>true</value>
</property>
<!-- 资源抢占策略 -->
<property>
    <name>yarn.resourcemanager.scheduler.monitor.policies</name>
    <value>org.apache.hadoop.yarn.server.resourcemanager.monitor.capacity.ProportionalCapacityPreemptionPolicy</value>
</property>
<!-- 抢占监测 d 额时间间隔 单位: 毫秒 -->
<property>
    <name>yarn.resourcemanager.monitor.preemption.monitoring_interval</name>
    <value>3000</value>
</property>
<!-- kill container 之前的等待时长 单位: 毫秒 -->
<property>
    <name>yarn.resourcemanager.monitor.capacity.preemption.max_wait_before_kill</name>
    <value>15000</value>
</property>
<!-- 每次监测触发抢占的资源的最大值(集群资源的百分比) -->
<property>
    <name>yarn.resourcemanager.monitor.capacity.preemption.total_preemption_per_round</name>
    <value>0.1</value>
</property>
<!-- 队列(超额)使用资源可忽略不进行抢占的百分比 -->
<!-- 即队列当前已使用资源超过了 capacity,但还未超过(1+0.1)*capacity,则对该队列进行抢占 -->
<property>
    <name>yarn.resourcemanager.monitor.capacity.preemption.max_ignored_over_capacity</name>
    <value>0.1</value>
</property>

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <property>
    <name>yarn.scheduler.capacity.root.queues</name>
    <value>default,queue_a,queue_b</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.default.capacity</name>
    <value>20</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.default.maximum-capacity</name>
    <value>100</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.default.accessible-node-labels</name>
    <value> </value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.default.user-limit-factor</name>
    <value>1</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.capacity</name>
    <value>40</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.maximum-capacity</name>
    <value>100</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.accessible-node-labels</name>
    <value> </value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.maximum-applications</name>
    <value>10000</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.maximum-am-resource-percent</name>
    <value>0.25</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.state</name>
    <value>RUNNING</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.acl_submit_applications</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.acl_administer_queue</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.queues</name>
    <value>sub_queue_a_1,sub_queue_a_2</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.capacity</name>
    <value>40</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.maximum-capacity</name>
    <value>100</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.accessible-node-labels</name>
    <value> </value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.maximum-applications</name>
    <value>10000</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.maximum-am-resource-percent</name>
    <value>0.25</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.state</name>
    <value>RUNNING</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.acl_submit_applications</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.acl_administer_queue</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.queues</name>
    <value>sub_queue_a,sub_queue_b</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_1.capacity</name>
    <value>50</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_1.maximum-capacity</name>
    <value>100</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_1.accessible-node-labels</name>
    <value> </value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_1.maximum-applications</name>
    <value>10000</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_1.maximum-am-resource-percent</name>
    <value>0.25</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_1.state</name>
    <value>RUNNING</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_1.acl_submit_applications</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_1.acl_administer_queue</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_2.capacity</name>
    <value>50</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_2.maximum-capacity</name>
    <value>100</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_2.accessible-node-labels</name>
    <value> </value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_2.maximum-applications</name>
    <value>10000</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_2.maximum-am-resource-percent</name>
    <value>0.25</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_2.state</name>
    <value>RUNNING</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_2.acl_submit_applications</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_a.sub_queue_a_2.acl_administer_queue</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_a.capacity</name>
    <value>50</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_a.maximum-capacity</name>
    <value>100</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_a.accessible-node-labels</name>
    <value> </value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_a.maximum-applications</name>
    <value>10000</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_a.maximum-am-resource-percent</name>
    <value>0.25</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_a.state</name>
    <value>RUNNING</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_a.acl_submit_applications</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_a.acl_administer_queue</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_b.capacity</name>
    <value>50</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_b.maximum-capacity</name>
    <value>100</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_b.accessible-node-labels</name>
    <value> </value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_b.maximum-applications</name>
    <value>10000</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_b.maximum-am-resource-percent</name>
    <value>0.25</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_b.state</name>
    <value>RUNNING</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_b.acl_submit_applications</name>
    <value>* *</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.queue_b.sub_queue_b.acl_administer_queue</name>
    <value>* *</value>
  </property>
</configuration>

发表回复