表達式引擎技術介紹及比較
()是一個開源的業務規則引擎,符合行業標準,快速高效。 它允許業務分析師或第一審閱者輕松查看業務規則,從而驗證編碼規則是否實現了所需的業務規則。
除了應用 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有一系列管理緩存的方式:
過去的性能建議很精彩