1. 程式人生 > >YARN的約束化標籤支援

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