【全链路】使用 Aviator 搞定监控系统里面的复合指标

609 阅读2分钟

1. 复合指标/叶子指标概念介绍

这里全链路监控中的指标来举例子。

指标 : [外呼未知异常占比]

指标 : [外呼未知异常量]

指标 : [外呼拨打量]

他们之间的关系是

外呼未知异常占比 = 外呼未知异常量 / 外呼拨打量。

那么 外呼未知异常占比 称之为复合指标,外呼未知异常量 / 外呼拨打量 称之为叶子指标。

对于叶子指标来说:叶子指标能够直接从es里面计算出来。

对于复合指标来说:es里面不能计算出来,需要计算出它依赖的叶子指标,然后在应用层进行计算。

2. aviator 快速入门

引入依赖

<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>5.1.4</version>
</dependency>

使用

public static void main(String[] args) {

    String expression = "( a + b ) * c";
    Expression compileExpression = AviatorEvaluator.compile(expression);
    // 获得表达式里面的所有参数
    // [a, b, c]
    System.out.println(compileExpression.getVariableNames());

    // 运算
    Object result = compileExpression.execute(
        new HashMap<String, Object>() {
            {
                put("a", 1);
                put("b", 2);
                put("c", 3);
            }
        }
    );


    // 打印结果 9 (2 + 1) * 3 = 9
    System.out.println(result);
}

3. 解决复合指标

所以当我们拿到一个复合指标时候,应该获得其表达式。然后提取表达式里面的叶子指标指标名字,然后通过叶子指标的指标名字去查询指标值,然后做运算。

但是上面的前提是建立在复合指标所依赖的指标都是叶子指标,要是复合指标所依赖的指标也是复合指标呢?

举个例子 :m = a / b , 其中 b = c + d , 那么上面的算法就不能解决这种问题。要解决这种问题的话 其实用递归就好了。

给出伪代码:



public class TestMain {


    private static final Map<String, Metrics> metricsMap = new HashMap<>();


    /**
     *  mock 指标元信息数据
     */
    static {
        metricsMap.put("m", new Metrics("m", "a + b"));
        metricsMap.put("a", new Metrics("a", null));
        metricsMap.put("b", new Metrics("b", "c + d"));
        metricsMap.put("c", new Metrics("c", null));
        metricsMap.put("d", new Metrics("d", null));
    }


    public static void main(String[] args) {
        System.out.println(calMetrics("m"));
    }


    /**
     * 指标计算
     * @param metricsName
     * @return
     */
    public static Object calMetrics(String metricsName) {
        Metrics metrics = mockGetMetricsByMetricsName(metricsName);
        if (metrics.getCalExpression() == null) {
            // 如果是叶子指标 直接计算
            return mockQueryMetricsValue(metricsName);
        } else {
            // 不是叶子指标

            // 表达式
            String calExpression = metrics.getCalExpression();
            Expression compileExpression = AviatorEvaluator.compile(calExpression);
            // 提取所有依赖的指标
            List<String> dependencyMetricsNames = compileExpression.getVariableNames();
            // 递归计算所依赖的指标
            Map<String, Object> dependencyMetricsValues = dependencyMetricsNames
                .stream()
                .collect(Collectors.toMap(
                    dependencyMetricsName -> dependencyMetricsName,
                    TestMain::calMetrics
                ));

            // 计算
            return compileExpression.execute(dependencyMetricsValues);
        }
    }


    /**
     * mock 通过指标名字查询指标元信息
     *
     * @param metricsName
     * @return
     */
    public static Metrics mockGetMetricsByMetricsName(String metricsName) {
        return metricsMap.get(metricsName);
    }


    /**
     * mock 通过指标名字查询指标值
     *
     * @param metricsName
     * @return
     */
    public static Object mockQueryMetricsValue(String metricsName) {
        switch (metricsName) {
            case "a":
                return 1;
            case "c":
                return 3;
            case "d":
                return 4;
            default:
                return null;
        }
    }
}


@Data
@RequiredArgsConstructor
class Metrics {
    /**
     * 指标名字
     */
    private final String metricsName;
    /**
     * 复合指标的计算表达式
     */
    private final String calExpression;
}


4. 源代码

aviator_demo: aviator_demo (gitee.com)