放置约束

概述

YARN 允许应用程序以数据局部性(对特定节点或机架的偏好)或(非重叠)节点标签的形式指定放置约束。本文档重点介绍 YARN 中更具表现力的放置约束。此类约束对于应用程序的性能和弹性至关重要,尤其是那些包含长期运行容器的应用程序,例如服务、机器学习和流工作负载。

例如,将作业的分配并置在同一机架上(亲和性约束)以降低网络成本、将分配分散在机器上(反亲和性约束)以最大程度地减少资源干扰,或允许在节点组中最多进行特定数量的分配(基数约束)以在两者之间取得平衡,这可能是有益的。放置决策也会影响弹性。例如,放置在同一集群升级域中的分配将同时脱机。

应用程序可以指定约束,而无需了解群集的底层拓扑(例如,无需指定容器应放置在哪个特定节点或机架上,以及无需指定已部署的其他应用程序)。目前,所有约束都是硬约束,也就是说,如果由于当前群集条件或冲突约束而无法满足容器的约束,则容器请求将保持挂起或被拒绝。

请注意,在此文档中,我们使用“分配”概念来指代在节点中分配的资源单元(例如,CPU 和内存)。在 YARN 的当前实现中,分配对应于单个容器。但是,如果应用程序使用分配来生成多个容器,则分配可能对应于多个容器。

快速指南

我们首先介绍如何启用具有放置约束的调度,然后提供使用分布式 shell(一个允许在一组容器上运行给定 shell 命令的应用程序)试验此功能的示例。

启用放置约束

要启用放置约束,必须将以下属性设置为 conf/yarn-site.xml 中的 placement-processorscheduler

属性 说明 默认值
yarn.resourcemanager.placement-constraints.handler 指定将用于处理 PlacementConstraints 的处理程序。可接受的值为:placement-processorschedulerdisabled disabled

我们现在详细介绍三个放置约束处理程序中的每一个

  • placement-processor:使用此处理程序,在调用容量或公平调度程序之前,将容器的放置确定为预处理步骤。一旦确定放置,就会调用容量/公平调度程序来执行实际分配。此处理程序的优点是它支持所有约束类型(亲和性、反亲和性和基数)。此外,它一次考虑多个容器,这比一次一个容器的方法可以满足更多约束。由于它位于主调度程序之外,因此容量和公平调度程序都可以使用它。请注意,目前它不考虑应用程序中的任务优先级,因为这些优先级可能与放置约束冲突。
  • scheduler:使用此处理程序,具有约束的容器将由主调度程序放置(截至目前,只有容量调度程序支持 SchedulingRequests)。它目前支持反亲和性约束(无亲和性或基数)。与 placement-processor 相比,此处理程序的优势在于,它遵循现有主调度程序强制执行的队列(按利用率、优先级排序)、应用(按 FIFO/公平性/优先级排序)和同一应用中的任务(优先级)的相同排序规则。
  • disabled:使用此处理程序,如果某个应用请求 SchedulingRequest,则将拒绝相应的分配调用。

placement-processor 处理程序支持更广泛的约束,并且可以允许放置更多容器,尤其是在应用具有苛刻的约束或集群利用率很高(由于一次考虑多个容器)的情况下。但是,如果尊重应用中的任务优先级对用户很重要并且使用了容量调度程序,则应使用 scheduler 处理程序。

使用分布式 Shell 试验放置约束

用户可以通过以下命令使用分布式 Shell 应用来试验放置约束

$ yarn org.apache.hadoop.yarn.applications.distributedshell.Client -jar share/hadoop/yarn/hadoop-yarn-applications-distributedshell-3.3.6.jar -shell_command sleep -shell_args 10 -placement_spec PlacementSpec

其中 PlacementSpec 的形式为

PlacementSpec               => "" | PlacementExpr;PlacementSpec
PlacementExpr               => SourceTag,ConstraintExpr
SourceTag                   => String(NumContainers)
ConstraintExpr              => SingleConstraint | CompositeConstraint
SingleConstraint            => "IN",Scope,TargetTag | "NOTIN",Scope,TargetTag | "CARDINALITY",Scope,TargetTag,MinCard,MaxCard | NodeAttributeConstraintExpr
NodeAttributeConstraintExpr => NodeAttributeName=Value, NodeAttributeName!=Value
CompositeConstraint         => AND(ConstraintList) | OR(ConstraintList)
ConstraintList              => Constraint | Constraint:ConstraintList
NumContainers               => int
Scope                       => "NODE" | "RACK"
TargetTag                   => String
MinCard                     => int
MaxCard                     => int

注意

  • 在分布式 Shell 命令中指定 -placement_spec 参数(NodeAttributeConstraintExpr 除外)时,不应使用 -num-containers 参数。如果将 -num-containers 参数与 -placement-spec 结合使用,则将忽略前者。这是因为在 PlacementSpec 中,我们确定每个标记的容器数量,从而使 -num-containers 变得多余且可能产生冲突。此外,如果使用了 -placement_spec,则所有容器都将请求 GUARANTEED 执行类型。
  • 如果指定了 NodeAttributeConstraintExpr,则 SourceTag(NumContainers) 是可选的,并且将考虑 -num-containers 的值来确定要请求的容器数量。

PlacementSpec 的示例如下

zk(3),NOTIN,NODE,zk:hbase(5),IN,RACK,zk:spark(7),CARDINALITY,NODE,hbase,1,3

上述内容对三个约束进行编码

  • 放置 3 个标记为“zk”(代表 ZooKeeper)的容器,它们之间具有节点反亲和性,即每个节点不放置多个容器(请注意,在此第一个约束中,约束的 SourceTag 和 TargetTag 相同);
  • 将 5 个带有标签“hbase”的容器放置到与运行带有标签“zk”的容器的机架具有亲和性的机架上(即,鉴于“zk”是第二个约束的目标标签,不应将“hbase”容器放置到运行“zk”容器的机架上);
  • 将 7 个带有标签“spark”的容器放置到至少有一个但至多有三个带有标签“hbase”的容器的节点中。

以下另一个示例演示了复合形式的约束

zk(5),AND(IN,RACK,hbase:NOTIN,NODE,zk)

上述约束使用连接运算符AND来组合两个约束。当其子约束都得到满足时,AND 约束得到满足。具体的 PlacementSpec 请求将 5 个“zk”容器放置到至少运行一个“hbase”容器的机架上,并且放置到没有运行“zk”容器的节点上。类似地,OR运算符可用于定义一个约束,当其至少一个子约束得到满足时,该约束得到满足。请注意,如果“zk”和“hbase”是属于不同应用程序的容器(在实际用例中很可能是这种情况),则 PlacementSpec 中的分配标签应包括命名空间,如下所述(请参阅分配标签命名空间)。

定义放置约束

分配标签

分配标签是应用程序可以与其(容器组)关联的字符串标签。标签用于标识应用程序的组件。例如,HBase Master 分配可以标记为“hbase-m”,而区域服务器可以标记为“hbase-rs”。其他示例包括“latency-critical”,用于指代分配的更一般的需求,或“app_0041”,用于表示作业 ID。分配标签在约束中发挥着关键作用,因为它们允许引用共享公共标签的多个分配。

请注意,我们使用新的SchedulingRequest对象来定义分配标签,而不是使用ResourceRequest对象。这与ResourceRequest有许多相似之处,但更好地分离了所请求分配的规模(分配的数量和规模、优先级、执行类型等)以及规定如何放置这些分配的约束(资源名称、松散局部性)。应用程序仍然可以使用ResourceRequest对象,但为了定义分配标签和约束,它们需要使用SchedulingRequest对象。在单个AllocateRequest中,应用程序应使用ResourceRequestSchedulingRequest对象,但不能同时使用这两个对象。

分配标签命名空间

分配标签可能引用相同或不同应用程序的容器,并分别用于表示应用程序内或应用程序间约束。我们使用分配标签名称空间来指定分配标签可以引用的应用程序范围。通过将分配标签与名称空间耦合,我们可以限制标签是否针对属于同一应用程序、特定应用程序组或集群中任何应用程序的容器。

我们目前支持以下名称空间

名称空间 语法 说明
SELF self/${allocationTag} 分配标签引用当前应用程序的容器(将应用约束)。这是默认名称空间。
NOT_SELF not-self/${allocationTag} 分配标签仅引用不属于当前应用程序的容器。
ALL all/${allocationTag} 分配标签引用任何应用程序的容器。
APP_ID app-id/${applicationID}/${allocationTag} 分配标签引用具有指定应用程序 ID 的应用程序的容器。
APP_TAG app-tag/application_tag_name/${allocationTag} 分配标签引用标记有指定应用程序标签的应用程序的容器。

要将分配标签名称空间 ns 附加到目标标签 targetTag,我们在 PlacementSpec 中使用语法 ns/allocationTag。请注意,默认名称空间是 SELF,用于应用程序内约束。其余名称空间标签用于指定应用程序间约束。当名称空间未指定在标签旁边时,假定为 SELF

上面使用的示例约束可以使用名称空间扩展如下

zk(3),NOTIN,NODE,not-self/zk:hbase(5),IN,RACK,all/zk:spark(7),CARDINALITY,NODE,app-id/appID_0023/hbase,1,3

这些约束的语义如下

  • 将 3 个带有标签“zk”(代表 ZooKeeper)的容器放置到没有运行其他应用程序的“zk”容器的节点上;
  • 将 5 个带有标签“hbase”的容器放置到机架上,该机架上运行着带有标签“zk”(来自任何应用程序,无论是相同还是不同的应用程序)的容器;
  • 将 7 个带有标签“spark”的容器放置到具有至少一个但不超过三个带有标签“hbase”的容器的节点上,这些容器属于 ID 为 appID_0023 的应用程序。

节点标签、节点属性和分配标签之间的差异

分配标签与节点标签或节点属性之间的差异在于,分配标签附加到分配而不是节点。当调度程序将分配分配给节点时,该分配的标签集将在分配期间自动添加到节点。因此,节点继承了当前分配给节点的分配的标签。同样,机架继承了其节点的标签。此外,与节点标签类似且与节点属性不同,分配标签没有附加值。正如我们在下面展示的,我们的约束可以引用分配标签以及节点标签和节点属性。

放置约束 API

应用程序可以在 PlacementConstraints 中使用公共 API 来构建放置约束。在描述用于构建约束的方法之前,我们先描述 PlacementTargets 类的用于构建目标表达式的使用方法,这些表达式随后将用于约束中

方法 说明
allocationTag(String... allocationTags) 在分配标签上构建一个目标表达式。如果存在具有给定标签之一的分配,则满足该表达式。
allocationTagWithNamespace(String namespace, String... allocationTags) 类似于 allocationTag(String...),但允许为给定的分配标签指定一个命名空间。
nodePartition(String... nodePartitions) 在节点分区上构建一个目标表达式。对于属于 nodePartitions 之一的节点,该表达式满足。
nodeAttribute(String attributeKey, String... attributeValues) 在节点属性上构建一个目标表达式。如果指定的节点属性具有指定的值之一,则满足该表达式。

请注意,上面的 nodeAttribute 方法尚不可用,因为它需要正在进行的节点属性功能。

用于构建约束的 PlacementConstraints 类的使用方法如下

方法 说明
targetIn(String scope, TargetExpression... targetExpressions) 创建一个约束,要求将分配放置在满足给定范围(例如,节点或机架)内所有目标表达式的节点上。例如,targetIn(RACK, allocationTag("hbase-m")) 允许在属于具有至少一个标签为“hbase-m”的分配的机架的节点上进行分配。
targetNotIn(String scope, TargetExpression... targetExpressions) 创建一个约束,要求将分配放置在属于不满足任何目标表达式的范围(例如,节点或机架)的节点上。
cardinality(String scope, int minCardinality, int maxCardinality, String... allocationTags) 创建一个约束,限制给定范围(例如,节点或机架)内的分配数量。例如,{@code cardinality(NODE, 3, 10, “zk”)} 在具有不少于 3 个标签为“zk”且不多于 10 个分配的节点上满足。
minCardinality(String scope, int minCardinality, String... allocationTags) 类似于 cardinality(String, int, int, String...),但仅确定最小基数(最大基数不受约束)。
maxCardinality(String scope, int maxCardinality, String... allocationTags) 类似于 cardinality(String, int, int, String...),但仅确定最大基数(最小基数为 0)。
targetCardinality(String scope, int minCardinality, int maxCardinality, String... allocationTags) 此约束对基数和目标约束进行了概括。考虑属于约束中指定范围的节点集 N。如果目标表达式在节点集 N 中至少满足 minCardinality 次且至多满足 maxCardinality 次,则约束得到满足。例如,targetCardinality(RACK, 2, 10, allocationTag("zk")),要求将分配放置在机架内,该机架至少有 2 个且至多有 10 个其他带有标签“zk”的分配。

PlacementConstraints 类还包括用于构建复合约束(具有多个约束的 AND/OR 表达式)的方法。添加对复合约束的支持是一项正在进行的工作。

在应用程序中指定约束

应用程序必须指定将为其启用每个约束的容器。为此,应用程序可以提供从一组分配标签(源标签)到放置约束的映射。例如,此映射的条目可以是“hbase”->constraint1,这意味着在调度带有标签“hbase”的每个分配时将应用 constraint1。

在使用 placement-processor 处理程序(请参阅 启用放置约束)时,此约束映射在 RegisterApplicationMasterRequest 中指定。

在使用 scheduler 处理程序时,还可以在每个 SchedulingRequest 对象中添加约束。每个此类约束都对该调度请求的标签有效。如果在 RegisterApplicationMasterRequest 和调度请求中都指定了约束,则后者将覆盖前者。