googleaviator——轻量级java表达式引擎实战(代码片段)

author author     2022-12-04     409

关键词:

表达式引擎技术及比较

Drools 简介

Drools(JBoss Rules )是一个开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。

除了应用了 Rete 核心算法,开源软件 License 和 100% 的Java实现之外,Drools还提供了很多有用的特性。其中包括实现了JSR94 API和创新的规则语义系统,这个语义系统可用来编写描述规则的语言。目前,Drools提供了三种语义模块

  • Python模块
  • Java模块
  • Groovy模块

Drools的规则是写在drl文件中。 对于前面的表达式,在Drools的drl文件描述为:

rule "Testing Comments"
when
    // this is a single line comment
    eval( true ) // this is a comment in the same line of a pattern
then
    // this is a comment inside a semantic code block
end

When表示条件,then是满足条件以后,可以执行的动作,在这里可以调用任何java方法等。在drools不支持字符串的contians方法,只能采用正则表达式来代替。 <!--more-->

IKExpression 简介

IK Expression 是一个开源的、可扩展的, 基于java 语言开发的一个超轻量级的公式化语言解析执行工具包。IK Expression 不依赖于任何第三方的 java 库。它做为一个简单的jar,可以集成于任意的Java 应用中。

对于前面的表达式,IKExpression 的写法为:

public static void main(String[] args) throws Throwable
    E2Say obj = new E2Say();
    FunctionLoader.addFunction("indexOf", 
                               obj, 
                               E2Say.class.getMethod("indexOf", 
                               String.class, 
                               String.class));
    System.out.println(ExpressionEvaluator.eval("$indexOf(\\"abcd\\",\\"ab\\")==0?1:0"));

可以看到 IK 是通过自定义函数 $indexOf 来实现功能的。

Groovy简介

Groovy经常被认为是脚本语言,但是把 Groovy 理解为脚本语言是一种误解,Groovy 代码被编译成 Java 字节码,然后能集成到 Java 应用程序中或者 web 应用程序,整个应用程序都可以是 Groovy 编写的——Groovy 是非常灵活的。

Groovy 与 Java 平台非常融合,包括大量的java类库也可以直接在groovy中使用。对于前面的表达式,Groovy的写法为:

Binding binding = new Binding();
binding.setVariable("verifyStatus", 1);
GroovyShell shell = new GroovyShell(binding);
boolean result = (boolean) shell.eval("verifyStatus == 1");
Assert.assertTrue(result);

Aviator简介

Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。现在已经有很多开源可用的java表达式求值引擎,为什么还需要Avaitor呢?

Aviator的设计目标是轻量级和高性能,相比于Groovy、JRuby的笨重,Aviator非常小,加上依赖包也才450K,不算依赖包的话只有70K;当然,

Aviator的语法是受限的,它不是一门完整的语言,而只是语言的一小部分集合。

其次,Aviator的实现思路与其他轻量级的求值器很不相同,其他求值器一般都是通过解释的方式运行,而Aviator则是直接将表达式编译成Java字节码,交给JVM去执行。简单来说,Aviator的定位是介于Groovy这样的重量级脚本语言和IKExpression这样的轻量级表达式引擎之间。对于前面的表达式,Aviator的写法为:

Map<String, Object> env = Maps.newHashMap();
env.put(STRATEGY_CONTEXT_KEY, context);

// triggerExec(t1) && triggerExec(t2) && triggerExec(t3)
log.info("### guid:  logicExpr: [  ], strategyData: ",
        strategyData.getGuid(), strategyData.getLogicExpr(), JSON.toJSONString(strategyData));

boolean hit = (Boolean) AviatorEvaluator.execute(strategyData.getLogicExpr(), env, true);

if (Objects.isNull(strategyData.getGuid())) 
    //若guid为空,为check告警策略,直接返回
    log.info("### strategyData:  check success", strategyData.getName());
    return;

性能对比

Drools是一个高性能的规则引擎,但是设计的使用场景和在本次测试中的场景并不太一样,Drools的目标是一个复杂对象比如有上百上千的属性,怎么快速匹配规则,而不是简单对象重复匹配规则,因此在这次测试中结果垫底。 IKExpression是依靠解释执行来完成表达式的执行,因此性能上来说也差强人意,和Aviator,Groovy编译执行相比,还是性能差距还是明显。

Aviator会把表达式编译成字节码,然后代入变量再执行,整体上性能做得很好。

Groovy是动态语言,依靠反射方式动态执行表达式的求值,并且依靠JIT编译器,在执行次数够多以后,编译成本地字节码,因此性能非常的高。对应于eSOC这样需要反复执行的表达式,Groovy是一种非常好的选择。

场景实战

监控告警规则

监控规则配置效果图:

最终转化成表达式语言可以表示为:

// 0.t实体逻辑如下

"indicatorCode": "test001",
"operator": ">=",
"threshold": 1.5,
"aggFuc": "sum",
"interval": 5,
"intervalUnit": "minute",
...


// 1.规则命中表达式
triggerExec(t1) && triggerExec(t2) && (triggerExec(t3) || triggerExec(t4))

// 2.单个 triggerExec 执行内部
indicatorExec(indicatorCode) >= threshold

此时我们只需调用 Aviator 实现表达式执行逻辑如下:

boolean hit = (Boolean) AviatorEvaluator.execute(strategyData.getLogicExpr(), env, true);

if (hit) 
    // 告警

自定义函数实战

基于上节监控中心内 triggerExec 函数如何实现

先看源码:

public class AlertStrategyFunction extends AbstractAlertFunction 

    public static final String TRIGGER_FUNCTION_NAME = "triggerExec";

    @Override
    public String getName() 
        return TRIGGER_FUNCTION_NAME;
    

    @Override
    public AviatorObject call(Map<String, Object> env, AviatorObject arg1) 
        AlertStrategyContext strategyContext = getFromEnv(STRATEGY_CONTEXT_KEY, env, AlertStrategyContext.class);
        AlertStrategyData strategyData = strategyContext.getStrategyData();
        AlertTriggerService triggerService = ApplicationContextHolder.getBean(AlertTriggerService.class);

        Map<String, AlertTriggerData> triggerDataMap = strategyData.getTriggerDataMap();
        AviatorJavaType triggerId = (AviatorJavaType) arg1;
        if (CollectionUtils.isEmpty(triggerDataMap) || !triggerDataMap.containsKey(triggerId.getName())) 
            throw new RuntimeException("cant find trigger config");
        

        Boolean res = triggerService.executor(strategyContext, triggerId.getName());
        return AviatorBoolean.valueOf(res);
    

按照官方文档,只需继承 AbstractAlertFunction ,即可实现自定义函数,重点如下:

  • getName() 返回 函数对应的调用名称,必须实现
  • call() 方法可以重载,尾部参数可选,对应函数入参多个参数分别调用使用

实现自定义函数后,使用前需要注册,源码如下:

AviatorEvaluator.addFunction(new AlertStrategyFunction());

如果在 Spring 项目中使用,只需在 bean 的初始化方法中调用即可。

踩坑指南 & 调优

使用编译缓存模式

默认的编译方法如 compile(script) 、 compileScript(path 以及 execute(script, env) 都不会缓存编译的结果,每次都将重新编译表达式,生成一些匿名类,然后返回编译结果 Expression 实例, execute 方法会继续调用 Expression#execute(env) 执行。

这种模式下有两个问题:

  1. 每次都重新编译,如果你的脚本没有变化,这个开销是浪费的,非常影响性能。
  2. 编译每次都产生新的匿名类,这些类会占用 JVM 方法区(Perm 或者 metaspace),内存逐步占满,并最终触发  full gc。

因此,通常更推荐启用编译缓存模式, compile 、 compileScript 以及 execute 方法都有相应的重载方法,允许传入一个 boolean cached 参数,表示是否启用缓存,建议设置为 true:

public final class AviatorEvaluatorInstance 
  public Expression compile(final String expression, final boolean cached)
  public Expression compile(final String cacheKey, final String expression, final boolean cached)
  public Expression compileScript(final String path, final boolean cached) throws IOException
  public Object execute(final String expression, final Map<String, Object> env,
      final boolean cached)      

其中的 cacheKey 是用来指定缓存的 key,如果你的脚本特别长,默认使用脚本作为 key 会占用较多的内存并耗费 CPU 做字符串比较检测,可以使用 MD5 之类唯一的键值来降低缓存开销。

缓存管理

AviatorEvaluatorInstance 有一系列用于管理缓存的方法:

  • 获取当前缓存大小,缓存的编译结果数量 getExpressionCacheSize() 
  • 获取脚本对应的编译缓存结果 getCachedExpression(script) 或者根据 cacheKey 获取 getCachedExpressionByKey(cacheKey) ,如果没有缓存过,返回 null。
  • 失效缓存 invalidateCache(script) 或者 invalidateCacheByKey(cacheKey) 。
  • 清空缓存 clearExpressionCache() 

性能建议

  • 优先使用执行优先模式(默认模式)。
  • 使用编译结果缓存模式,复用编译结果,传入不同变量执行。
  • 外部变量传入,优先使用编译结果的 Expression#newEnv(..args) 方法创建外部 env,将会启用符号化,降低变量访问开销。
  • 生产环境切勿打开执行跟踪模式。
  • 调用 Java 方法,优先使用自定义函数,其次是导入方法,最后是基于 FunctionMissing 的反射模式。

往期精彩

参考: [1].Drools, IKExpression, Aviator和Groovy字符串表达式求值比较 [2].AviatorScript 编程指南

aviator表达式引擎轻量级的java语言实现的表达式求值引擎(代码片段)

文章目录Aviator表达式引擎轻量级的java语言实现的表达式求值引擎`Process`简介`Process`实现原理`Process`特性`Process`尝试`Process`内置常量、变量、表达式`Process`模式选择`Process`依赖Aviator表达式引擎轻量级的java语言实现的表达式求值... 查看详情

规则表达式引擎框架

...丰富的EL表达式,7倍于SpringEL的超高性能。Aviator——轻量级高性能Java表达式引擎Aviator是一个高性能、轻量级的Java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。支持大部分运算操作符、支持函数调用和... 查看详情

java8新特性-lambda

...称为箭头函数,匿名函数,闭包。(2)lambda表达式体现的是轻量级函数式编程思想。(3)->符号式lambda表达式核心操作符号,符号左侧是操作函数,右侧是操作表达式。(4)jdk1.8新特性 2.ModelCodeAsDate(1)ModelCodeAsDate,编码及数据,... 查看详情

规则表达式引擎框架

...丰富的EL表达式,7倍于SpringEL的超高性能。Aviator——轻量级高性能Java表达式引擎Aviator是一个高性能、轻量级的Java 查看详情

aviator使用手册在线

Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。现在已经有很多开源可用的java表达式求值引擎,为什么还需要Avaitor呢?Aviator的设计目标是轻量级和高性能,相比于Groovy、JRuby的笨重,Avi... 查看详情

lamdaexpression

...的出现,从一定程度上改变了代码的编写方式,提供了更轻量级的语法实现。为了快速理解这一概念我们通过下面的示例代码进行理解:JavaLambdaExpression出现前的函数式接口编码方式(‘重量级&rd 查看详情

常用规则引擎

...:easy-rules首先集成了mvel表达式,后续可能集成SpEL的一款轻量级规则引擎easyrules是一个简单而强大的java规则引擎,它有以下特性:轻量级框架,学习成本低基于POJO为定义业务引擎提供有用的抽象和简便的应用从原始的规则组合... 查看详情

使用lambda编写九九乘法表

...lambda表达式非常的赞。lambda表达式即匿名方法,属于一种轻量级的封装lambda表达式的语法由参数列表、箭头符号->和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:(intx,inty)->x+y()->5(Strings)->{System.out.print... 查看详情

规则引擎easyrules介绍,应用及示例(代码片段)

浅析EasyRules规则引擎轻量级规则引擎EasyRulesJava规则引擎EasyRules一、EasyRules介绍1.1概述EasyRules是一个简单而强大的Java规则引擎,提供以下功能:轻量级框架和易于学习的API基于POJO的开发与注解的编程模型定义抽象的业务... 查看详情

非常强大的java时间处理工具类!(代码片段)

...种常用日期格式化模板,支持Java8时间类和Date,轻量级,无第三方依赖。 为什 查看详情

一个php实现的轻量级简单爬虫(代码片段)

最近需要收集资料,在浏览器上用另存为的方式实在是很麻烦,而且不利于存储和检索。所以自己写了一个小爬虫,在网上爬东西,迄今为止,已经爬了近百万张网页。现在正在想办法着手处理这些数据。爬... 查看详情

牛逼,java中表达式引擎工具就用它!建议收藏,一定用的到!!(代码片段)

...于做反射)不依赖任何第三方库,因此整体非常轻量级,整个jar包大小哪怕发展到现在5.0这个大版本,也才430K。同时,Aviator内置的函数库非常“节制”,除了必须的字符串处理、数学函数和集合处理之外&... 查看详情

读java8函数式编程笔记03_高级集合类和收集器

1. 方法引用1.1. 一种引用方法的轻量级语法1.1.1. 提供了一种简短的语法1.1.2. 标准语法为Classname::methodName1.2. 凡是使用Lambda表达式的地方,就可以使用1.3. 自动支持多个参数1.3.1. 前提是选对了正确的函数... 查看详情

java并发编程:synchronized底层优化(偏向锁轻量级锁)

...ynchronized及其实现原理Java并发编程:Synchronized底层优化(轻量级锁、偏向锁)Java并发编程:线程间的协作(wait/notify/sleep/yield/join)Java并发编程:volatile的使用及其原理一、重量级锁  上篇文章中向大家介绍了Synchronized的用法及... 查看详情

jquery简介

核心知识点:1.jQuery是什么(一个轻量级的、兼容多浏览器的JavaScript库)2.jQuery的优点(轻量级、丰富的DOM选择器、链式表达式、动画样式事件支持、兼容多浏览器、支持扩展开发)3.引入jQuery的两种方式  a.本地文件<scriptsr... 查看详情

轻量orm-sqlrepoex(十七)sqlrepoex2.30版本更新说明

...,支持MySQL、SQLServer数据库方言,使用强类型操作数据的轻量级ORM工具,在减少魔法字串同时,通过灵活的Lambda表达式组合,实现业务数据查询的多样性。如果想找到替代EF方案,SqlRepoEx是比较好的选择。SqlRepoEx已经应用于多个... 查看详情

一课掌握lambda表达式语法及作用简单入门1??

...可以成为箭头函数,匿名函数,闭包Lambda表达式体现的是轻量级函数方式编程思想JDK8新特性->左边操作参数,右侧是操作表达式ModelCodeasData编码及数据,尽可能轻量级的将代码封装为数据解决方案:接口&实现类(匿名内部类)... 查看详情

Java的轻量级B树库? [关闭]

】Java的轻量级B树库?[关闭]【英文标题】:LightweightB-treelibraryforJava?[closed]【发布时间】:2011-07-2114:15:23【问题描述】:谁能推荐一个用于Java的轻量级、快速且希望稳定的B-tree(或类似)库?基本上我正在寻找磁盘上的地图;类... 查看详情