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