背景
渠道路由规则执行使用了Drools,Drools 目前只支持java语言。基于golang 比较流行的规则引擎grule-rule-engine、gengine、govaluate、gval 做对比
路由使用规则场景
- 基础规则执行能力
- 支持操作符:>、<、>=、<=、!=、=、contain、in、not in、
- 支持规则string初始化规则上下文
- 规则支持规则中增加日志
- 规则string支持修改对象变量
golang规则引擎竞品对比
grule-rule-engine | gengine | govaluate | |
项目地址 | https://github.com/hyperjumptech/grule-rule-engine | https://github.com/bilibili/gengine | https://github.com/Knetic/govaluate |
star&fork | 1.7K Start 284forkhyperjump 开源基于drools 思想开发,语法与drools相似 | 1.6K230forkbilbili开源 | 3.2K431fork个人开源 |
最近维护时间 | 2020年开源,最近几月有维护 | 2020年开源 2021年10月后没有维护 | 2017年后没有更新过代码 |
算法 | 抽象规则树AST(Abstract Syntax Tree) 底层基于antlr | 抽象规则树AST(Abstract Syntax Tree)底层基于antlr | 表达式引擎-golang语法 |
是否支持路由场景 | 原生支持string in,不支持slice&array contain(自定义函数嵌入规则实现)支持自定义执行顺序 | 支持规则支持自定义函数(不支持string in & slice&array contain)支持自定义执行顺序 | 不支持contain,支持in 表达式引擎,小而美的组件 |
语法 | DSL 自定义规则语法 类似Drools | DSL 自定义规则语法 | golang 原生语法 |
文档 | 比较丰富,有demo及文档,中文文档较少 | 中文文档较丰富。目前不维护状态 | 文档较少 |
基础测试benchmark测试(Benchmark 基准测试)
Benchmark指标对比
goos: darwin
goarch: amd64
pkg: templement-web/biz
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
每次初始化上下文
BenchmarkFunction1-12 1249 1047944 ns/op 529804 B/op 9449 allocs/op
BenchmarkFunction-12 240 4800814 ns/op 3309257 B/op 40973 allocs/op
复用上下文:
BenchmarkFunction-12 97605 11035 ns/op 2358 B/op 82 allocs/op
指标解释:
ns/op = 每次执行纳秒
B/op = 每次执行申请字节
allocs/op= 每次执行申请内存次数
package main
// require github.com/hyperjumptech/grule-rule-engine v1.13.0
import (
"fmt"
"github.com/hyperjumptech/grule-rule-engine/ast"
"github.com/hyperjumptech/grule-rule-engine/builder"
"github.com/hyperjumptech/grule-rule-engine/engine"
"github.com/hyperjumptech/grule-rule-engine/pkg"
"testing"
)
type TestCar struct {
SpeedUp bool
Speed int64
MaxSpeed int64
SpeedIncrement int64
}
type DistanceRecord struct {
TotalDistance int64
}
const s = "rule SpeedUp \"When testcar is speeding up we keep increase the speed.\" salience 10 " +
" {\n when\n TestCar.SpeedUp==true && TestCar.Speed < TestCar.MaxSpeed\n " +
" then\n TestCar.Speed = TestCar.Speed + TestCar.SpeedIncrement;\n " +
"DistanceRecord.TotalDistance = DistanceRecord.TotalDistance + TestCar.Speed; Retract(\"SpeedUp\");\n}"
// 缓存上下文
func BenchmarkFunction(b *testing.B) {
b.ResetTimer()
drls := s
knowledgeLibrary := ast.NewKnowledgeLibrary()
ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary)
err1 := ruleBuilder.BuildRuleFromResource("TutorialRules", "0.0.1", pkg.NewBytesResource([]byte(drls)))
if err1 != nil {
panic(err1)
}
knowledgeBase := knowledgeLibrary.NewKnowledgeBaseInstance("TutorialRules", "0.0.1")
for i := 0; i < b.N; i++ {
myFact := &TestCar{
SpeedUp: true,
Speed: 200,
MaxSpeed: 500,
SpeedIncrement: 100,
}
myFactDistanceRecord := &DistanceRecord{
TotalDistance: 1000,
}
i2 := myFact.MaxSpeed + myFactDistanceRecord.TotalDistance
dataCtx := ast.NewDataContext()
err := dataCtx.Add("TestCar", myFact)
err = dataCtx.Add("DistanceRecord", myFactDistanceRecord)
if err != nil {
panic(err)
}
engineresult := engine.NewGruleEngine()
err = engineresult.Execute(dataCtx, knowledgeBase)
if err != nil {
panic(err)
}
if i2 != myFactDistanceRecord.TotalDistance {
fmt.Println("not equal")
} else {
fmt.Println("equal")
}
}
b.StopTimer()
}
// 每次初始化上下文
func BenchmarkFunction1(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
drls := s
knowledgeLibrary := ast.NewKnowledgeLibrary()
ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary)
err1 := ruleBuilder.BuildRuleFromResource("TutorialRules", "0.0.1", pkg.NewBytesResource([]byte(drls)))
if err1 != nil {
panic(err1)
}
knowledgeBase := knowledgeLibrary.NewKnowledgeBaseInstance("TutorialRules", "0.0.1")
myFact := &TestCar{
SpeedUp: true,
Speed: 200,
MaxSpeed: 500,
SpeedIncrement: 100,
}
myFactDistanceRecord := &DistanceRecord{
TotalDistance: 1000,
}
i2 := myFact.MaxSpeed + myFactDistanceRecord.TotalDistance
dataCtx := ast.NewDataContext()
err := dataCtx.Add("TestCar", myFact)
err = dataCtx.Add("DistanceRecord", myFactDistanceRecord)
if err != nil {
panic(err)
}
engineresult := engine.NewGruleEngine()
err = engineresult.Execute(dataCtx, knowledgeBase)
if err != nil {
panic(err)
}
if i2 != myFactDistanceRecord.TotalDistance {
fmt.Println("not equal")
} else {
fmt.Println("equal")
}
}
b.StopTimer()
}
package main
// require github.com/bilibili/gengine v1.5.7
import (
"fmt"
"github.com/bilibili/gengine/builder"
"github.com/bilibili/gengine/context"
"github.com/bilibili/gengine/engine"
"testing"
"time"
)
type TestCar struct {
SpeedUp bool
Speed int64
MaxSpeed int64
SpeedIncrement int64
}
type DistanceRecord struct {
TotalDistance int64
}
const pass_status_test = `
rule "1" "1"
begin
if (TestCar.SpeedUp==true && TestCar.Speed < TestCar.MaxSpeed) {
TestCar.Speed = TestCar.Speed + TestCar.SpeedIncrement
DistanceRecord.TotalDistance = DistanceRecord.TotalDistance + TestCar.Speed
}
end
`
func BenchmarkFunction(b *testing.B) {
b.StartTimer()
for i := 0; i < b.N; i++ {
myFact := &TestCar{
SpeedUp: true,
Speed: 200,
MaxSpeed: 500,
SpeedIncrement: 100,
}
myFactDistanceRecord := &DistanceRecord{
TotalDistance: 1000,
}
dataContext := context.NewDataContext()
dataContext.Add("TestCar", myFact)
dataContext.Add("DistanceRecord", myFactDistanceRecord)
//init rule engine
ruleBuilder := builder.NewRuleBuilder(dataContext)
//resolve rules from string
err := ruleBuilder.BuildRuleFromString(pass_status_test)
if err != nil {
panic(err)
}
eng := engine.NewGengine()
// true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule
err = eng.Execute(ruleBuilder, true)
if err != nil {
panic(err)
}
}
b.StopTimer()
}
star 历史对比
语法对比
Drools
/*
rule rulename
when
then
end
**/
规则定义
rule "SpeedUp"
salience 10
when
$TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
$DistanceRecord : DistanceRecordClass()
then
$TestCar.setSpeed($TestCar.Speed + $TestCar.SpeedIncrement);
update($TestCar);
$DistanceRecord.setTotalDistance($DistanceRecord.getTotalDistance() + $TestCar.Speed);
update($DistanceRecord);
end
/*代码使用
KieHelper kieHelper = new KieHelper();
kieHelper.addContent("规则体", ResourceType.DRL);
Results verify = kieHelper.verify();
KieSession kieSession = kieHelper.getKieContainer().newKieSession();;
KieSession kieSession = context.getKieSession();
kieSession.setGlobal("log", log);
// 塞入变量
kieSession.insert(new TestCarClass());
kieSession.insert(new DistanceRecordClass());
int count = kieSession.fireAllRules();
*/
grule-rule-engine
/*
rule 规则名称 规则描述 salience 执行优先级
when
then
**/
rule SpeedUp "When testcar is speeding up we keep increase the speed." salience 10 {
when
TestCar.SpeedUp == true && TestCar.Speed < TestCar.MaxSpeed
then
TestCar.Speed = TestCar.Speed + TestCar.SpeedIncrement;
DistanceRecord.TotalDistance = DistanceRecord.TotalDistance + TestCar.Speed;
}
/*
//构建上线文
knowledgeLibrary := ast.NewKnowledgeLibrary()
ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary)
err1 := ruleBuilder.BuildRuleFromResource("TutorialRules", "0.0.1", pkg.NewBytesResource([]byte(drls)))
if err1 != nil {
panic(err1)
}
knowledgeBase := knowledgeLibrary.NewKnowledgeBaseInstance("TutorialRules", "0.0.1")
dataCtx := ast.NewDataContext()
err := dataCtx.Add("TestCar", testCar)
err := dataCtx.Add("DistanceRecord", distanceRecord)
// 执行规则
engineresult := engine.NewGruleEngine()
err = engineresult.Execute(dataCtx, knowledgeBase)
*/
gengine
//rule 规则名称 规则描述 salience 执行优先级
//begin
//end
// 不能以; 结尾,否则规则解析异常
rule "SpeedUp" "When testcar is speeding up we keep increase the speed."
begin
if(TestCar.SpeedUp == true && TestCar.Speed < TestCar.MaxSpeed){
TestCar.Speed = TestCar.Speed + TestCar.SpeedIncrement
DistanceRecord.TotalDistance = DistanceRecord.TotalDistance + TestCar.Speed
}
end
/*
dataContext := context.NewDataContext()
//inject struct
rs := &TestCar{SpeedUp: false}
rd := &DistanceRecord{TotalDistance: 100}
dataContext.Add("TestCar",rs)
dataContext.Add("DistanceRecord",rd)
//init rule engine
ruleBuilder := builder.NewRuleBuilder(dataContext)
//读取规则
e1 := ruleBuilder.BuildRuleFromString(pass_status_rule)
if e1 != nil {
panic(e1)
}
eng := engine.NewGengine()
e2 := eng.Execute(ruleBuilder, true)
*/
govaluate
u := &TestCar{SpeedUp: false}
parameters := make(map[string]interface{})
parameters["u"] = u
expr, _ := govaluate.NewEvaluableExpression("u.SpeedUp()==true")
result, _ := expr.Evaluate(parameters)
fmt.Println("user", result)
满足情况对比
grule-rule-engine | gengine | govaluate | |
操作符 | 可以支持原生+自定义函数(contain) | 可以支持原生+自定义函数(in、contain) | 不支持 in、contain小而美组件 |
规则中修改变量 | 支持 | 支持 | 不支持 |
规则中断 | Complete() | 支持,使用return | 不支持 |
支持模板 | 不支持可以使用go 内置templatefor循环实现 | 不支持可以使用go 内置templatefor循环实现 | 不支持可以使用go 内置templatefor循环实现 |
日志 | 支持传入日志 | 支持传入日志 | 不支持,显示打印 |
缓存规则上下文 | 支持(https://github.com/hyperjumptech/grule-rule-engine/discussions/342) | 不支持(每次都需要初始化) | 不支持 |
调研结论
- grule-rule-engine、gengine 都可以满足java drools 迁移到 go
- grule-rule-engine 语法与Drools 比较相似
- grule-rule-engine 可以缓存上下文,执行速度较快
- gengine 中文文档较多,目前不维护状态
偏向于grule-rule-engine