前言
Drools是java语言的规则引擎,本文是针对go语言的规则引擎框架
先说场景:以一个电商运维场景为例,我们需要对用户注册年限p1、购买金额p2、地域p3等条件给用户进行发券,基于条件进行任意组合成不同规则。比如:
- 规则1 :p1 > 2 && p2 > 10 000 & p3 in (‘beijng’,’shanghai’) 大于2年的老用户,并且购买金额大于10000的北京或上海用户。
- 规则2:p1<1 小于1年的用户
if... else ...
Go 规则引擎
先说结论:比较了govaluate、goengine、gorule,最终使用govaluate。相比 gorule、goengine,govaluate除了支持in操作、还支持正则表达式,而且表达式也不需要转换成DRL。
- 支持string类型的 ==操作
- 支持in操作
- 支持计算逻辑表达式和算数表达式
- 支持正则
Go 规则引擎对比
框架 |
功能 |
基准测试 |
---|---|---|
govaluate https://github.com/Knetic/govaluate Star: 1.4 k |
1、直接使用表达式。不需要转IDL 2、支持算数表达式和逻辑表达式。3、支持string的判断。4、支持in 操作。1 in (1,2,3)字符串 in 。‘code1’ in (‘cod1′,’code2’) 5、支持正则 |
测试3个逻辑表达式条件,如下:每次执行op需要15us |
gengie(B站开源) https://github.com/rencalo770/gengine Star: 193 |
规则表达式类似于一个DRL。规则脚本具有if..else等语法,后续支持扩展灵活。 |
|
grue https://github.com/hyperjumptech/grule-rule-engine/ star:525 |
规则表达式需要生成生成一个 DRL。 |
|
https://github.com/dop251/goja star:1.8k |
go实现执行js脚本(类似于java 执行groovy ) 这里不关注表达式,只是通过这引擎可以执行js脚本代码。goja、gengine、grule都是基于脚本的,只是gojar是支持js,grule和gengine是自定义的语法脚本。 |
govaluate
func TestGoValueate() {
// 支持多个逻辑表达式
expr, err := govaluate.NewEvaluableExpression("(10 > 0) && (2.1 == 2.1) && 'service is ok' == 'service is ok'" +
" && 1 in (1,2) && 'code1' in ('code3','code2',1)")
if err != nil {
log.Fatal("syntax error:", err)
}
result, err := expr.Evaluate(nil)
if err != nil {
log.Fatal("evaluate error:", err)
}
fmt.Println(result)
// 逻辑表达式包含变量
expression, err := govaluate.NewEvaluableExpression("http_response_body == 'service is ok'")
parameters := make(map[string]interface{}, 8)
parameters["http_response_body"] = "service is ok"
res, _ := expression.Evaluate(parameters)
fmt.Println(res)
// 算数表达式包含变量
expression1, _ := govaluate.NewEvaluableExpression("requests_made * requests_succeeded / 100")
parameters1 := make(map[string]interface{}, 8)
parameters1["requests_made"] = 100
parameters1["requests_succeeded"] = 80
result1, _ := expression1.Evaluate(parameters1)
fmt.Println(result1)
}
基准测试
func BenchmarkNewEvaluableExpression(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := govaluate.NewEvaluableExpression("(10 > 0) && (100 > 20) && 'code1' in ('code3','code2',1)")
if err != nil {
log.Fatal("syntax error:", err)
}
}
}
func BenchmarkEvaluate(b *testing.B) {
parameters1 := make(map[string]interface{}, 8)
parameters1["gmv"] = 100
parameters1["customerId"] = "80"
parameters1["stayLength"] = 20
for i := 0; i < b.N; i++ {
_, err := govaluate.NewEvaluableExpression("(gmv > 0) && (stayLength > 20) && customerId in ('80','code2','code3')")
if err != nil {
log.Fatal("syntax error:", err)
}
}
}
在 测试 go 文件目录下执行
go test -bench=. ./
或者
go test -bench=. -benchmem
测试结果
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkNewEvaluableExpression-12 78542 14692 ns/op 8592 B/op 139 allocs/op
BenchmarkEvaluate-12 77352 14884 ns/op 8960 B/op 138 allocs/op
参考资料
- https://github.com/Knetic/govaluate
- https://andrewpqc.github.io/2020/01/12/govaluate-expression-engine/
- https://segmentfault.com/a/1190000022235609
- http://www.heartthinkdo.com/?p=3711
- https://studygolang.com/articles/27630