机会容器

目的

本文档介绍了机会容器执行的概念,并讨论了机会容器的分配和执行方式。

快速指南

我们首先简要概述机会容器,包括用户如何启用此功能以及使用此类容器运行示例作业。

主要目标

与仅在存在未分配资源时才在节点中计划的现有 YARN 容器不同,即使无法立即在该节点上启动执行,也可以将机会容器分派到 NM。在这种情况下,机会容器将在该 NM 中排队,直到资源可用。机会容器执行的主要目标是提高集群资源利用率,从而增加任务吞吐量。对于包含相对较短任务(以秒为单位)的工作负载,资源利用率和任务吞吐量的提高更为明显。

启用机会容器

要启用机会容器分配,以下两个属性必须存在于 conf/yarn-site.xml

属性 说明 默认值
yarn.resourcemanager.opportunistic-container-allocation.enabled 启用机会容器分配。 false
yarn.nodemanager.opportunistic-containers-max-queue-length 确定可在 NM 中排队的机会容器的最大数量。 0

上面的第一个参数必须设置为 true。第二个参数必须设置为正值,以允许在 NM 中对机会容器排队。值为 10 可用于开始尝试使用机会容器。最佳值取决于作业特性、集群配置和目标利用率。

默认情况下,机会容器的分配是通过 RM 集中执行的。但是,用户可以选择启用机会容器的分布式分配,这可以进一步提高短任务的分配延迟。通过将以下参数设置为 true,可以启用分布式计划(请注意,非机会容器将继续通过 RM 计划)

属性 说明 默认值
yarn.nodemanager.distributed-scheduling.enabled 启用分布式计划。 false

为了向已启用 AMRMProxy 的集群提交作业,必须为提交作业的客户端创建一组单独的配置。在这些配置中,conf/yarn-site.xml 应具有以下其他配置

属性 说明
yarn.resourcemanager.scheduler.address localhost:8049 将作业重定向到节点管理器的 AMRMProxy 端口。

运行示例作业

MapReduce PI

可以使用以下命令运行示例 pi map-reduce 作业,使用机会容器执行 40% 的映射器

$ hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar pi -Dmapreduce.job.num-opportunistic-maps-percent="40" 50 100

通过更改上述命令中 mapreduce.job.num-opportunistic-maps-percent 的值,我们可以指定可通过机会容器执行的映射器的百分比。

分布式 Shell

另一个示例作业是分布式 shell,它允许我们在容器组上运行给定的 shell 命令。以下命令可用于在 10 个机会容器中运行sleep 10命令

$ yarn org.apache.hadoop.yarn.applications.distributedshell.Client -jar share/hadoop/yarn/hadoop-yarn-applications-distributedshell-3.3.6.jar.jar -shell_command sleep -shell_args 10 -num_containers 10 -container_type OPPORTUNISTIC

通过在上述命令中将container_type的值更改为OPPORTUNISTICGUARANTEED,我们可以指定在机会容器或保证容器中运行的任务。默认类型为GUARANTEED。通过向上述命令添加标志-promote_opportunistic_after_start,应用程序主机会尝试在启动所有机会容器后将其全部提升为保证容器。通过向上述命令添加标志‘-enforce_execution_type’,调度程序将遵守容器的执行类型。

Web UI 中的机会容器

启用机会容器分配后,可以在 Web UI 的节点页面(rm-address:8088/cluster/nodes)中观察到以下新列

  • 正在运行的容器 (O):每个节点上正在运行的机会容器数;
  • 已用内存 (O):每个节点上机会容器使用的总内存;
  • 已用虚拟内核 (O):每个节点上机会容器使用的总 CPU 虚拟内核;
  • 已排队容器:每个节点上排队的容器数。

单击节点上正在运行的特定容器时,还会显示容器的执行类型。

在本文档的其余部分中,我们将提供机会容器的深入描述,包括有关其分配和执行的详细信息。

概述

YARN 中的现有调度程序(公平调度程序和容量调度程序)仅在调度容器时该节点有未分配资源的情况下才将容器分配给节点。这种保证执行类型具有以下优点:一旦 AM 将容器调度到节点,容器执行将立即开始,因为可以保证有可用资源。此外,除非违反公平性或容量约束,否则可以保证容器运行到完成而不会被抢占。

虽然此设计提供了更可预测的任务执行,但它有两个主要缺点,可能导致集群资源利用率不佳

  • 反馈延迟。当容器在节点上完成执行时,RM 会通过下一个 NM-RM 心跳收到有关可用资源的通知,然后 RM 在该节点上调度一个新容器,AM 通过下一个 AM-RM 心跳收到通知,最后 AM 在节点上启动新容器。这些延迟会导致节点资源空闲,进而导致资源利用率降低,尤其是在工作负载涉及持续时间相对较短的任务时。
  • 已分配资源与已利用资源。RM 根据每个节点的已分配资源分配容器,这可能显著高于实际已利用资源(例如,考虑一个已分配 4GB 内存但仅利用 2GB 的容器)。这会降低有效的资源利用率,如果 RM 在调度期间考虑已利用资源,则可以避免这种情况。但是,必须以一种方式来完成此操作,以便在运行容器的已利用资源增加时回收资源。

为了缓解上述问题,除了现有容器(我们在此之后称之为保证容器)之外,我们引入了机会性容器的概念。即使在调度时没有可用的(未分配的)资源,也可以将机会性容器调度到 NM。在这种情况下,机会性容器将在 NM 处排队,等待资源可用以开始执行。机会性容器的优先级低于保证容器,这意味着可以抢占它们以使保证容器开始执行。因此,它们可用于提高集群资源利用率,而不会影响现有保证容器的执行。

机会性容器的另一个优点是,它们引入了NM 上的执行优先级的概念。例如,不需要严格执行保证的低优先级作业可以使用机会性容器或其任务的容器执行类型组合。

我们引入了两种分配机会性容器的方法:集中式分布式。在集中式调度中,通过 YARN RM 分配机会性容器,而在分布式调度中,通过驻留在每个 NM 上的本地调度程序分配机会性容器。集中式分配允许做出更高质量的放置决策,并为跨应用程序实施更复杂的共享策略(例如,公平性)。另一方面,分布式调度可以提供更快的容器分配,这对于短任务很有用,因为它避免了往返 RM 的过程。在这两种情况下,保证容器的调度保持不变,并通过 YARN RM(使用现有的 Fair 或 Capacity Scheduler)进行。

请注意,在当前实现中,我们根据已分配(而非已利用)资源分配容器。因此,我们解决了上面提到的“反馈延迟”问题,但没有解决“已分配与已利用资源”问题。目前正在进行的工作 (YARN-1011) 使用机会性容器来解决后一个问题。

下面,我们将更详细地描述容器执行类型,以及机会容器的执行(包括在 NM 中对容器进行排队)和分配。然后,我们将讨论如何通过一些高级配置参数对机会容器进行微调。最后,我们将讨论未来工作的开放式问题。

容器执行类型

我们引入了以下两种类型的容器

  • 保证容器对应于现有的 YARN 容器。它们由公平或容量调度程序分配,一旦分派到节点,便保证有可用的资源立即开始执行。此外,这些容器运行至完成(只要没有故障)。只有当它们所属的调度程序队列违反公平性或容量约束时,才可能被抢占。
  • 机会容器在分派到节点时,并不能保证有资源开始执行。相反,它们可能会在 NM 中排队,直到有资源可用。如果一个保证容器到达一个节点,并且没有可用的资源,则一个或多个机会容器将被抢占以执行保证容器。

当 AM 向 RM 提交其资源请求时,它为每个容器指定类型(默认值为保证),确定容器的分配方式。随后,当 AM 在 NM 中启动容器时,其类型决定了 NM 将如何执行它。

机会容器的执行

当一个容器到达 NM 时,其执行由 NM 中的可用资源和容器类型决定。保证容器立即开始执行,如果需要,NM 将终止正在运行的机会容器,以确保有足够的资源供保证容器启动。另一方面,如果在机会容器到达 NM 时没有可用的资源来启动其执行,则它们可以在 NM 中排队。为了实现这一点,我们通过允许在每个节点排队容器来扩展 NM。NM 监视本地资源,当有足够的可用资源时,它将启动队列首部的机会容器的执行。

特别是,当一个容器到达 NM 时,将执行本地化(即,下载所有必需的资源),然后容器移动到SCHEDULED状态,在该状态下容器排队,等待其执行开始

  • 如果有可用资源,则容器的执行将立即开始,无论其执行类型如何。
  • 如果没有可用资源
    • 如果容器得到保证,我们就会杀死尽可能多的正在运行的机会容器,以保证保证的容器能够执行,然后开始执行它。
    • 如果容器是机会容器,它将保留在队列中,直到资源可用。
  • 当一个容器(保证的或机会的)完成执行并且资源得到释放时,我们检查队列中的容器,如果存在可用资源,我们开始执行它们。我们按先进先出顺序从队列中挑选容器。

在下面的未来工作项目中,我们讨论了对任务执行(队列重新排序)进行优先级排序和杀死机会容器为保证容器腾出空间的不同方法。

机会容器的分配

如上所述,我们提供了集中式和分布式两种分配机会容器的方式,我们将在下面进行描述。

集中式分配

我们在 RM 中引入了一项新服务,即 OpportunisticContainerAllocatorAMService,它扩展了 ApplicationMasterService。当启用集中式机会分配时,AM 的资源请求由 OpportunisticContainerAllocatorAMService 在 RM 端提供服务,它将这些请求拆分为两组资源请求

  • 保证集被转发到现有的 ApplicationMasterService,随后由 Fair 或 Capacity Scheduler 处理。
  • 机会集由新的 OpportunisticContainerAllocator 处理,它执行机会容器到节点的调度。

OpportunisticContainerAllocator 维护一个列表,其中包含集群中每时每刻的负载最低的节点,并以循环方式向它们分配容器。请注意,在当前实现中,我们故意不考虑节点局部性约束。由于机会容器(与保证容器不同)可能会在其执行开始前在 NM 的队列中等待,因此将其分配到负载较低的节点(即队列延迟较小)比遵守其局部性约束更为重要。此外,我们目前不考虑机会容器的共享(公平性/容量)约束。如果需要,将来可以添加对局部性和共享约束的支持。

分布式分配

为了启用机会容器的分布式调度,我们在每个 NM 中引入了一项新服务,称为 AMRMProxyServiceAMRMProxyService 实现了 ApplicationMasterService 协议,并充当在该节点上运行的 AM 与 RM 之间的代理。当启用 AMRMProxyService(通过参数)时,我们将强制在特定节点上运行的所有 AM 与同一节点的 AMRMProxyService 通信,而不是直接访问 RM。此外,为了确保 AM 不会直接与 RM 通信,当新的 AM 初始化时,我们将用由 AMRMProxyService 签名的令牌替换其 AMRMToken

可以向 AMRMProxyService 注册一系列拦截器。其中一个拦截器是 DistributedScheduler,它负责以分布式方式分配机会容器,而无需联系 RM。此模块化设计使 AMRMProxyService 在其他场景中也很有用,例如 YARN 联合(YARN-2915)或限制行为不当的 AM,只需在拦截器链中添加其他拦截器即可启用这些功能。

当启用分布式机会调度时,每个 AM 会将其资源请求发送到在同一节点上运行的 AMRMProxyServiceAMRMProxyService 将资源请求分成两组

  • 已保证的组会转发到 RM。在这种情况下,AMRMProxyService 只是充当 AM 与 RM 之间的代理,并且容器分配保持不变(使用公平或容量调度程序)。
  • 机会组不会转发到 RM。相反,它由在节点上本地运行的 DistributedScheduler 处理。具体来说,DistributedScheduler 维护一个列表,其中包含集群中最少负载的节点,并以循环方式向它们分配容器。RM 通过 NM-RM 心跳定期向 DistributedScheduler 报告最少负载的节点。

上述过程类似于上面描述的集中式机会调度中 OpportunisticContainerAllocatorAMService 执行的过程。主要区别在于,在分布式情况下,将请求拆分为已保证和机会请求在节点本地发生,并且只有已保证的请求会转发到 RM,而机会请求则在不联系 RM 的情况下处理。

确定要分配的节点

每个 NM 会通过 NM-RM 心跳定期向 RM 报告正在运行的已保证和机会容器的数量,以及排队的机会容器的数量。RM 从所有节点收集此信息,并确定最少负载的节点。

在机会容器的集中分配的情况下,此信息可立即获得,因为分配是集中进行的。在分布式调度的情况下,最少加载节点的列表通过 RM 对 NM 的心跳响应传播到所有 NM(因此对DistributedSchedulers可用)。发送到 NM 的最少加载节点数是可配置的。

目前,我们仅考虑每个节点上排队的机会容器的数量,以估计如果将机会容器发送到该节点,它将需要等待的时间,从而确定最少加载的节点。如果 AM 向我们提供了有关估计任务持续时间的信息,我们可以考虑这些信息,以便更好地估计队列等待时间。

重新平衡节点负载

有时可能会对机会容器做出较差的放置选择(由于队列长度估计过时),这会导致节点之间的负载不平衡。在集群负载较高的情况下,这个问题更为明显,并且在分布式调度的情况下也是如此(多个DistributedSchedulers可能会将容器放置在同一 NM 上,因为它们不会相互协调)。为了解决 NM 队列之间的这种负载不平衡,我们执行负载分流以动态地在 NM 之间重新平衡负载。具体来说,在 RM 中汇总每个 NM 发布的队列时间估计时,我们构建一个分布并找到 NM 队列长度的目标最大值(基于分布的平均值和标准差)。然后,RM 通过心跳响应将此值传播到各个 NM。随后,NM 使用此信息,如果节点的队列长度高于阈值,则会放弃机会容器以满足此最大值。这迫使关联的各个 AM 在其他地方重新调度这些容器。

高级配置

启用机会容器分配并在集中式和分布式分配之间进行选择的主要属性已在本文档开头的快速指南中进行了描述。这里我们介绍更高级的配置。请注意,在大多数情况下,使用这些参数的默认值就足够了。以下所有参数都必须在conf/yarn-site.xml文件中定义。

为了确定在调度机会容器时将使用的最少加载节点的数量以及刷新此列表的频率,我们使用以下参数

属性 说明 默认值
yarn.resourcemanager.opportunistic-container-allocation.nodes-used 机会容器分配器在容器分配期间调度容器时要使用的最少加载节点数。较高的值可以改善大型集群中的负载平衡。 10
yarn.resourcemanager.nm-container-queuing.sorting-nodes-interval-ms 计算最少加载节点的频率。 1000

如上文 节点负载重新平衡 部分所述,RM 会定期收集所有 NM 队列长度,并计算其平均值 (avg) 和标准差 (stdev),以及值 avg + k*stdev (其中 k 为浮点数)。此值通过 NM-RM 心跳传播到所有 NM,只要其当前队列长度介于 queue_min_lengthqueue_max_length 值之间(这些值分别用于避免从非常短的队列中取消排队任务和积极地从长队列中取消排队任务),所有 NM 都应尊重该值。可以按如下方式指定参数 kqueue_min_lengthqueue_max_length

属性 说明 默认值
yarn.resourcemanager.nm-container-queuing.queue-limit-stdev k 参数。 1.0f
yarn.resourcemanager.nm-container-queuing.min-queue-length queue_min_length 参数。 5
yarn.resourcemanager.nm-container-queuing.max-queue-length queue_max_length 参数。 15

最后,如果使用分布式调度,还可以通过另外两个属性进一步调整 AMRMProxyService

属性 说明 默认值
yarn.nodemanager.amrmproxy.address AMRMProxyService 绑定的地址/端口。 0.0.0.0:8049
yarn.nodemanager.amrmproxy.client.thread-count 每个 NM 中用于为不同作业向 AMRMProxyService 提供拦截器注册服务的线程数。 3

未来工作事项

在此,我们描述了多种可用于扩展/增强机会性容器的分配和执行的方法。我们还提供了跟踪每个事项的 JIRA。

  • 资源超额提交 (YARN-1011)。如前所述,为了进一步提高集群资源利用率,我们可以根据实际使用的资源而不是分配的资源来调度容器。在超额提交资源时,如果已运行容器的已用资源增加,则存在耗尽资源的风险。因此,机会性执行应用于分配超出节点容量的容器。通过这种方式,我们可以选择机会性容器以回收资源。
  • NM 队列重新排序 (YARN-5886)。我们可以采用重新排序策略,动态确定接下来要执行哪个机会性容器,而不是按 FIFO 顺序执行排队容器。例如,我们可以优先考虑预计运行时间短或属于接近完成的应用程序的容器。
  • 在 NMs 中无序终止 (YARN-5887)。如上所述,当我们需要释放资源以保证容器开始执行时,我们会按到达顺序反向终止机会容器(首先终止最近启动的容器)。这可能并不总是正确的决定。例如,我们可能希望最大程度地减少终止的容器数量,或避免终止即将完成的作业的容器。
  • 容器暂停 (YARN-5292):目前,我们在资源争用时终止机会容器,为保证容器腾出空间。在繁忙的集群中,这会降低有效的集群利用率:每当我们终止一个正在运行的机会容器时,都必须重新启动它,因此会丢失工作。为此,我们可以暂停正在运行的机会容器。请注意,这将需要容器执行程序(例如,所使用的容器技术)和应用程序的支持。
  • 容器提升 (YARN-5085)。在某些情况下,在容器执行期间更改其执行类型可能是有益的。例如,应用程序可能会将容器提交为机会容器,并在其执行开始时,它可以请求将其提升为保证容器,以避免被终止。