表达式引擎技术介绍及比较
()是一个开源的业务规则引擎,符合行业标准,快速高效。 它允许业务分析师或第一审阅者轻松查看业务规则,从而验证编码规则是否实现了所需的业务规则。
除了应用 Rete 的核心算法、开源软件和 100% Java 实现外,它还提供了许多有用的功能。 其中包括JSR94 API的实现和创新的规则语义系统,可以用来编写描述规则的语言。目前提供了三个语义模块
规则写在drl文件中。 对于上面的表达式,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技术。 在不支持字符串的方式下,只能用正则表达式代替。
介绍
IK是一个基于java语言开发的开源、可扩展、超轻量级的公式语言分析和执行工具包。 IK 不依赖于任何第三方 java 库。 它以一个简单的 jar 形式出现,可以集成到任何 Java 应用程序中。
对于上面的表达式,写法是:
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.evaluate("$indexOf("abcd","ab")==0?1:0"));
}
复制代码
可以看出IK是通过自定义函数$来实现功能的。
介绍
它通常被认为是一种脚本语言,将其理解为脚本语言是一种误解。 代码被编译成 Java 字节码,然后可以集成到 Java 应用程序或 Web 应用程序中。 可以编译整个应用程序。 - 非常灵活。
特别是与Java平台集成,包括大量的java泛型也可以直接在里面使用。 对于上面的表达式,写法是:
Binding binding = new Binding();
binding.setVariable("verifyStatus", 1);
GroovyShell shell = new GroovyShell(binding);
boolean result = (boolean) shell.evaluate("verifyStatus == 1");
Assert.assertTrue(result);
复制代码
介绍
是一个用java语言实现的高性能、轻量级的表达式求值引擎,主要用于各种表达式的动态求值。 已经有很多开源的 java 表达式求值引擎可用,为什么我们需要它们?
JRuby 的设计目标是轻量级和高性能。 相对于JRuby的笨重机械效率的定义及表达式,它非常小巧,依赖包只有450K,不包含依赖包也只有70K; 实际上,
句型有限,不是完整的语言,只是语言集中的一小部分。
其次,实现思路与其他轻量级评估器有很大不同。 其他的求值器通常以解释的形式运行,而是直接将表达式编译成Java字节码,交给JVM执行。 . 简单来说,语言的定位是介于这样一个重量级的脚本语言和这样一个轻量级的表达式引擎之间。 对于上面的表达式,写法是:
Map 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;
}
复制代码
性能比较
是一个高性能的规则引擎,设计的使用场景和本次测试的场景不一样。 目标是具有数百或数千个属性的复杂对象,如何快速匹配规则,而不是简单的对象重复匹配规则,因此在本次测试中排在最后。 表达式的执行是通过解释和执行的方式完成的,所以表现不尽如人意。 与编译执行相比,性能差异还是很明显的。
它会将表达式编译成字节码,然后在执行前将其代入一个变量。 整体表现相当不错。
它是一种动态语言,借助反射的方式动态地执行表达式求值,并借助于JIT编译器,在执行足够的次数后,将其编译成本地字节码,因此性能非常高。 对于需要反复执行的表达式,比如eSOC,是非常好的选择。
场景实战监控告警规则
监控规则配置效果图:
最终转化为表达语言可以表示为:
// 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
复制代码
此时,我们只需要调用实现表达式,执行逻辑如下:
boolean hit = (Boolean) AviatorEvaluator.execute(strategyData.getLogicExpr(), env, true);
if (hit) {
// 告警
}
复制代码
自定义函数实践
如何在上一节的基础上实现监控中心的内部功能
先看源码:
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 env, AviatorObject arg1) {
AlertStrategyContext strategyContext = getFromEnv(STRATEGY_CONTEXT_KEY, env, AlertStrategyContext.class);
AlertStrategyData strategyData = strategyContext.getStrategyData();
AlertTriggerService triggerService = ApplicationContextHolder.getBean(AlertTriggerService.class);
Map triggerDataMap = strategyData.getTriggerDataMap();
AviatorJavaType triggerId = (AviatorJavaType) arg1;
if (CollectionUtils.isEmpty(triggerDataMap) || !triggerDataMap.containsKey(triggerId.getName())) {
throw new RuntimeException("can't find trigger config");
}
Boolean res = triggerService.executor(strategyContext, triggerId.getName());
return AviatorBoolean.valueOf(res);
}
}
复制代码
根据官方文档,只需要继承n就可以实现自定义功能,关键点如下:
实现自定义功能后,需要先注册后才能使用。 源代码如下:
AviatorEvaluator.addFunction(new AlertStrategyFunction());
复制代码
如果在项目中使用,只需要在bean的初始化方法中调用即可。
踩坑手册&使用编译缓存模式调优
(,(path,(,env)等默认编译方法不会缓存编译结果,每次都会重新编译表达式,生成一些匿名类,然后返回编译结果实例,trick会继续调用#(环境)实施。
这些模式有两个问题:
每次都重新编译,如果你的脚本没有改变,这种开销是浪费的,尤其会影响性能。 每次编译都会形成一个新的匿名类,这个类会占用JVM模式区(Perm或),显存会逐渐填满,最终会触发。
为此,一般建议开启编译缓存模式,方法有对应的重载方法,允许传入一个参数来表示是否开启缓存。 建议设置为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 env,
final boolean cached)
}
复制代码
其中就是用来指定缓存的key。 如果你的脚本很长,默认使用脚本作为key会占用更多的显存和消耗CPU进行字符串比较测量。 您可以使用唯一通配符(例如 MD5)来增加缓存成本。 .
缓存管理
ance有一系列管理缓存的方式:
过去的性能建议很精彩