YARN的约束化标签支持

前言


在比较早期的时候,YARN就已经实现了具有分片功能的node label功能,以此确保应用资源的使用隔离。我们可以理解为这种标签是单一维度的,假设我们有多维度标签使用的需求时,这种node label就不是那么好用了。当然,你可以说,我们可以构建多个标签,一个节点同时赋予多个维度标签,但是其实这同样不会那么好操作。今天笔者要阐述的就是YARN node label特性的升级版特性:Constraint nodel label(约束化标签)。简单的理解,我们可以把它理解为是一种个性化标签。

Constraint node label


约束化标签相较于原本标签功能,它的灵活性大大提升了。

首先,它是支持多维度,多类型标签。比如标签可以是boolean型,int型或者string型。定义多个标签类型是为了可以表示不同的维度含义,比如我们可以用boolean类型的标签来表示某节点是否是期待的操作系统类型,比如!Windows的含义就是当前的操作系统不是Windows系统。int类型的我们可以表示节点磁盘数量信息等。通过上述多类型的标签表示,可以精确具体地描述出一个节点的特征。

第二点,基于多维度标签的表达式匹配。因为约束化标签是支持多类型的,对应条件匹配模式当然也不仅仅是只有int或not in这样的关系。它还可以支持如下的条件操作原语文

1.包含关系判断: in, not in
2.值大小判断:<,>,>=,=,<=
3.条件运算操作关系:&&,//

通过以上3种条件的拼接组合,我们可以组合出非常灵活的条件判断规则,比如下面这个例子:NUM_DISK > 3 && !WINDOWS上述条件表示的含义是节点需要同时满足磁盘数大于3并且操作系统不是WINDOWS系统2个特征。这个表达式很好的体现了约束化标签中“约束”的含义。基于多维度的特征定义和条件过滤,用户可以做出更精确的标签识别。

约束化标签实现原理


下面我们来了解约束化标签的具体实现。在代码实现层面,我们如何来设计并实现约束化标签的功能呢?

要实现这个功能,我们首先要明确与约束化标签相关的一些概念,然后定义出相应类以及内部方法。以下是目前YRAN约束化标签对此的具体设计。

第一个,约束值类,主要包含实际值以及比较操作值,比如long类型的约束值类定义如下:

  static class LongConstraintValue implements ConstraintValue {
    // 标签值
    private long value = 0;
    // 与外部值的比较操作运算符
    private ExpressionCompareOp compareOp;

    ...

    /**
     * 与外部值的比较方法
     */
    @Override
    public boolean matches(ConstraintValue targetValue) {
      assert (targetValue instanceof LongConstraintValue);
      double nodeConstraintVal = ((LongConstraintValue) targetValue).value;
      // 根据具体操作符做值的比较
      switch (compareOp) {
      case EQUAL:
        return value == nodeConstraintVal;
      case NOT_EQUAL:
        return value != nodeConstraintVal;
      case GREATER_OR_EQUAL:
        return nodeConstraintVal >= value;
      case GREATER_THAN:
        return nodeConstraintVal > value;
      case LESS_THAN:
        return nodeConstraintVal < value;
      case LESS_OR_EQUAL:
        return nodeConstraintVal <= value;
      default:
        return false;
      }
    }

当然,对于string类型,就是EQUAL和NOT_EQUAL类型的。

第二个,操作符类型,就是代码里里提到的,目前总共我们会涉及到以下可能用到的操作符语义。

public enum ExpressionCompareOp {
  LESS_THAN, // '<' or 'lt'
  LESS_OR_EQUAL, // '<=' or 'le'
  EQUAL, // '==' or 'eq'
  NOT_EQUAL, // '!=' or 'ne' or 'ene' (for exists but not equal)
  GREATER_OR_EQUAL, // '>=' or 'ge'
  GREATER_THAN, // '>' or 'gt'
  IN, // 'in'
  NOT_IN, // 'not_in'
  EXISTS, // no operand only if the name is specified
  NOT_EXISTS, // '! <constrainName>'
}

第三个,约束比较类型,我们可以理解为它是一个策略类,它定义了不同类型值进行比较的方式,根据输入不同的比较原语操作符。下面是此类的接口定义:

/**
 * 定义了值进行比较的方式,根据输入不同的比较原语操作.
 */
public interface ConstraintType {
  /**
   * 类型名
   */
  String getConstraintTypeName();

  /**
   * 支持的操作原语
   */
  Set<ExpressionCompareOp> getSupportedCompareOperation();

  /**
   * 根据输入操作原语,返回具体约束值类
   */
  ConstraintValue getConstraintValue(ExpressionCompareOp compareOp)
      throws YarnException;
}

我们来看其中的一个具体子类,

public class LongConstraintType implements ConstraintType {
  // 此类型策略可支持的所以操作符
  private Set<ExpressionCompareOp> supportedOps =
      new HashSet<>(Arrays.asList(ExpressionCompareOp.values()));

  @Override
  public String getConstraintTypeName() {
    return ConstraintsUtil.LONG_CONSTRAINT_TYPE;
  }

  @Override
  public Set<ExpressionCompareOp> getSupportedCompareOperation() {
    return supportedOps;
  }

  @Override
  public ConstraintValue getConstraintValue(ExpressionCompareOp compareOp) {
    // 根据比较操作符,返回不同的约束值类,然后约束值里再进行具体的比较操作
    switch (compareOp) {
    case IN:
      return new LongSetConstraintValue(true);
    case NOT_IN:
      return new LongSetConstraintValue(true);
    default:
      return new LongConstraintValue(compareOp);
    }
  }

第四点,约束特征表达式,约束特征表达式可以理解为就是一条完整的表达式条件,它包括了各个子条件约束值实例,以及连接运算表达式(与,或关系),如下:

public class ConstraintExpressionList extends ConstraintExpression {

  /**
   * 操作运算符,与,或操作
   */
  @Private
  @Unstable
  public static enum Operator {
    AND, OR
  }

  private Operator operator;
  // 包含的子表达式,因为可能存在嵌套的条件判断
  private List<ConstraintExpression> expressionList =
      new ArrayList<ConstraintExpression>();

  public ConstraintExpressionList(ConstraintExpression... expressions) {
    this(Operator.AND, expressions);
  }

  public ConstraintExpressionList(Operator op,
      ConstraintExpression... expressions) {
    this.operator = op;
    this.expressionList =
        new ArrayList<ConstraintExpression>(Arrays.asList(expressions));
  }

  ...

  /**
   * 条件匹配判断
   */
  @Override
  public boolean evaluate(Map<String, ConstraintValue> nodeConstraintMappings) {
    for (ConstraintExpression constraintExpression : expressionList) {
      if (constraintExpression.evaluate(nodeConstraintMappings)) {
        // 操作符如果是或关系,有表达式判断成立即为成功
        if (operator == Operator.OR) {
          return true;
        }
      } else if (operator == Operator.AND) {
        // 操作符如果是与关系,有表达式判断不匹配即为成功
        return false;
      }
    }
    // 没有条件判断,直接比操作符
    return (operator == Operator.AND);
  }

因为在表达式中,存在嵌套条件的可能,所以这里表达式有以下2两类:

  • LIST列表类型,LIST类型表达式里面可能存在子列表型或者COMPARE型。
  • COMPARE操作比较型。

比如下面这个表达式,就是LIST类型和COMPARE表达式的组合。

(NUM_DISK >= 3 && !WINDOWS) || JDK_VERSION >= 1.8

上述表达式,可以看做一个大LIST类型表达式 = (小LIST表达式(2个COMPARE表达式组成))+ COMPARE表达式。

所以在表达式定义这块,最为复杂和重要的其实是表达式的解析这块。由于篇幅有限,笔者这里就不展开具体阐述了,解析类代码已上传至文章末尾的链接处,感兴趣的同学可以深入继续深入了解了解,主要的一个思路方向是借助于堆栈结构,从左往右解析约束表达式字符串。

利用以上3大类型定义,就能构造出非常灵活的标签描述以及特征表达式的判断了。
基于这些表达式的判断,RM可以做约束标签的节点筛选和过滤 ,后续的Container分配过程与普通node label的过程基本一致。

引用


[1].https://issues.apache.org/jira/browse/YARN-3409. Support Node Attribute functionality
[2].https://issues.apache.org/jira/secure/attachment/12937633/Node-Attributes-Requirements-Design-doc_v2.pdf
[3].https://github.com/linyiqun/hadoop-yarn/tree/master/ConstraintNodeLabel

posted @ 2020-01-12 19:08  回眸,境界  阅读(71)  评论(0编辑  收藏  举报