Golang 规则引擎原理及实战
本文主要介绍规则引擎在 golang 中的使用,将首先介绍 golang 中主要的规则引擎框架,然后利用 golang 原生的 parser 搭建一个简单的规则引擎实现基本的 bool 表达式解析工作。
背景
随着业务代码的不断迭代,诞生出了越来越多的 if-else,并且 if-else 中的逻辑越来越复杂,导致代码逻辑复杂、维护性差、可读性差、修改风险高等缺陷。
复杂的 if-else 逻辑其实对应的是一条条的规则,满足对应的规则在执行对应的操作,即 if-else 中的条件就是一个对应的 bool 表达式:
|--bool 表达式--|
if a == 1 && b == 2 {// do some business
}
一个复杂的逻辑表示一条对应的规则,将这些规则用 bool 表达式表示之后,便可以按照对应的规则执行操作,大大减少 if-else 的应用:
if 规则 {// do some business
}
而如何解析这些 bool 表达式便是规则引擎所需要完成的任务了。
规则引擎介绍
规则引擎由是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。
Golang语言实现的主要规则引擎框架:
名称 | 地址 | 规则描述语言 | 使用场景 | 使用复杂性 |
---|---|---|---|---|
YQL(Yet another-Query-Language) | https://github.com/caibirdme/yql | 类SQL | 表达式解析 | 低 |
govaluate | https://github.com/Knetic/govaluate | 类Golang | 表达式解析 | 低 |
Gval | https://github.com/PaesslerAG/gval | 类Golang | 表达式解析 | 低 |
Grule-Rule-Engine | https://github.com/hyperjumptech/grule-rule-engine | 自定义DSL(Domain-Specific Language) | 规则执行 | 中 |
Gengine | https://github.com/bilibili/gengine | 自定义DSL(Domain-Specific Language) | 规则执行 | 中 |
Common Expression Language | https://github.com/google/cel-go#evaluate | 类C | 通用表达式语言 | 中 |
goja | https://github.com/dop251/goja | JavaScript | 规则解析 | 中 |
GopherLua: VM and compiler for Lua in Go. | https://github.com/yuin/gopher-lua | lua | 规则解析 | 高 |
可以看到无数的人在前仆后继地造规则引擎,但是这些规则引擎由于功能强大,因此对于一些比较简单的逻辑表达式的解析任务来说就显得有点重了。
比如想使用规则引擎实现如下的规则,例如如上的这些框架来实现解析的话会大量消耗 CPU 的资源,在请求量较大的系统当中就有可能成为系统的性能屏障。
if type == 1 && product_id = 3{//...
}
因此需要一个简单轻便性能较好的规则引擎。
基于Go parser库打造规则引擎
parser 库介绍
Go 内置的 parser 库提供了 golang 底层语法分析的相关操作,并且其相关的 api 向用户开放,那么便可以直接使用 Go 的内置 parser 库 完成上面一个基本规则引擎的框架。
针对如下的规则表达式使用go原生的parser进行解析(规则中不能使用 type 关键字):
// 使用go语法表示的bool表达式,in_array为函数调用
expr := `product_id == "3" && order_type == "0" && in_array(capacity_level, []string{"900","1100"}) && carpool_type == "0"`// 使用go parser解析上述表达式,返回结果为一颗ast
parseResult, err := parser.ParseExpr(expr)
if err != nil {fmt.Println(err)return
}// 打印该ast
ast.Print(nil, parseResult)
可以得到如下的结果(一颗二叉树):
0 *ast.BinaryExpr {
1 . X: *ast.BinaryExpr {
2 . . X: *ast.BinaryExpr {
3 . . . X: *ast.BinaryExpr {
4 . . . . X: *ast.Ident {
5 . . . . . NamePos: 1
6 . . . . . Name: "product_id"
7 . . . . }
8 . . . . OpPos: 12
9 . . . . Op: ==
10 . . . . Y: *ast.BasicLit {
11 . . . . . ValuePos: 15
12 . . . . . Kind: STRING
13 . . . . . Value: "\"3\""
14 . . . . }
15 . . . }
16 . . . OpPos: 19
17 . . . Op: &&
18 . . . Y: *ast.BinaryExpr {
19 . . . . X: *ast.Ident {
20 . . . . . NamePos: 22
21 . . . . . Name: "order_type"
22 . . . . }
23 . . . . OpPos: 33
24 . . . . Op: ==
25 . . . . Y: *ast.BasicLit {
26 . . . . . ValuePos: 36
27 . . . . . Kind: STRING
28 . . . . . Value: "\"0\""
29 . . . . }
30 . . . }
31 . . }
32 . . OpPos: 40
33 . . Op: &&
34 . . Y: *ast.CallExpr {
35 . . . Fun: *ast.Ident {
36 . . . . NamePos: 43
37 . . . . Name: "in_array"
38 . . . }
39 . . . Lparen: 51
40 . . . Args: []ast.Expr (len = 2) {
41 . . . . 0: *ast.Ident {
42 . . . . . NamePos: 52
43 . . . . . Name: "capacity_level"
44 . . . . }
45 . . . . 1: *ast.CompositeLit {
46 . . . . . Type: *ast.ArrayType {
47 . . . . . . Lbrack: 68
48 . . . . . . Elt: *ast.Ident {
49 . . . . . . . NamePos: 70
50 . . . . . . . Name: "string"
51 . . . . . . }
52 . . . . . }
53 . . . . . Lbrace: 76
54 . . . . . Elts: []ast.Expr (len = 2) {
55 . . . . . . 0: *ast.BasicLit {
56 . . . . . . . ValuePos: 77
57 . . . . . . . Kind: STRING
58 . . . . . . . Value: "\"900\""
59 . . . . . . }
60 . . . . . . 1: *ast.BasicLit {
61 . . . . . . . ValuePos: 83
62 . . . . . . . Kind: STRING
63 . . . . . . . Value: "\"1100\""
64 . . . . . . }
65 . . . . . }
66 . . . . . Rbrace: 89
67 . . . . . Incomplete: false
68 . . . . }
69 . . . }
70 . . . Ellipsis: 0
71 . . . Rparen: 90
72 . . }
73 . }
74 . OpPos: 92
75 . Op: &&
76 . Y: *ast.BinaryExpr {
77 . . X: *ast.Ident {
78 . . . NamePos: 95
79 . . . Name: "carpool_type"
80 . . }
81 . . OpPos: 108
82 . . Op: ==
83 . . Y: *ast.BasicLit {
84 . . . ValuePos: 111
85 . . . Kind: STRING
86 . . . Value: "\"0\""
87 . . }
88 . }
89 }
打造基于parser库的规则引擎
将 parser 解析出来的这颗二叉树画出来:
可以看到,有了 Golang 原生的语法解析器,我们只需要后序遍历这棵二叉树,然后实现一套 AST 与对应数据map的映射关系即可实现一个简单的规则引擎。
其中,AST 与对应数据map的映射关系的实现代码的主要结构如下:
func eval(expr ast.Expr, data map[string]interface{}) interface{} {switch expr := expr.(type) {case *ast.BasicLit: // 匹配到数据return getlitValue(expr)case *ast.BinaryExpr: // 匹配到子树// 后序遍历x := eval(expr.X, data) // 左子树结果y := eval(expr.Y, data) // 右子树结果if x == nil || y == nil {return errors.New(fmt.Sprintf("%+v, %+v is nil", x, y))}op := expr.Op // 运算符// 按照不同类型执行运算switch x.(type) {case int64:return calculateForInt(x, y, op)case bool:return calculateForBool(x, y, op)case string:return calculateForString(x, y, op)case error:return errors.New(fmt.Sprintf("%+v %+v %+v eval failed", x, op, y))default:return errors.New(fmt.Sprintf("%+v op is not support", op))}case *ast.CallExpr: // 匹配到函数return calculateForFunc(expr.Fun.(*ast.Ident).Name, expr.Args, data)case *ast.ParenExpr: // 匹配到括号return eval(expr.X, data)case *ast.Ident: // 匹配到变量return data[expr.Name]default:return errors.New(fmt.Sprintf("%x type is not support", expr))}
}
完整的实现代码在这里:go_parser
性能对比
使用基于 go parser 实现的规则引擎对比其他常见的规则引擎(YQL、govaluate、gval)的性能:
BenchmarkGoParser_Match-8 127189 8912 ns/op // 基于 go parser 实现的规则引擎
BenchmarkGval_Match-8 63584 18358 ns/op // gval
BenchmarkGovaluateParser_Match-8 13628 86955 ns/op // govaluate
BenchmarkYqlParser_Match-8 10364 112481 ns/op // yql
总结
可以看到在使用原生的 parser 实现的规则引擎在性能上具有较大的优势,但缺点在于需要自己实现一套 AST 与对应数据map的映射关系,并且受限于 go 原生 parser 库的限制导致规则的定义语言比较繁琐,这些也都是为什么会有其他规则引擎框架诞生的原因,但不可否认基于原生 parser 库打造的规则引擎的性能还是足够优秀的,因此在一些比较简单的规则匹配场景中还是优先考虑使用原生 parser,可以最大效率的实现降本增效的效果。
Golang 规则引擎原理及实战相关推荐
- 阿里开源规则引擎QLExpress-入门实战
介绍 规则引擎,顾名思义是针对我们业务系统中普世的规则进行统一管理,通过该引擎进行调度计算,可以动态调整规则的表达式内容,而不影响业务系统代码,常见的业务典型场景有电商中促销活动,单品折扣.整场活动满 ...
- 开源规则引擎 drools
前言 在很多企业的 IT 业务系统中,经常会有大量的业务规则配置,而且随着企业管理者的决策变化,这些业务规则也会随之发生更改.为了适应这样的需求,我们的 IT 业务系统应该能快速且低成本的更新.适应这 ...
- 基于Rete算法的JAVA规则引擎
作者:张渊 夏清国( 西北工业大学计算机学院, 西安710072) 出自:<科学技术与工程> 第 6 卷第 11 期 2006 年 6 月 摘要 在软件应用中若能抽取出规则, 可以使软件实 ...
- drools 6.5 -规则引擎入门
本文结构: 1. Drools 2. Drools 规则引擎原理 3. 入门例子 1. Drools Drools 具有一个易于访问企业策略.易于调整以及易于管理的开源业务 规则引擎,符合业内标准,速 ...
- 技术分享 | EdgeX 规则引擎 eKuiper 实战
关于2022 EdgeX中国挑战赛 2022 EdgeX中国挑战赛暨中关村国际前沿科技创新大赛EdgeX专题赛正式拉开帷幕.大赛由北京市科委.中关村管委会指导,由Linux基金会主办,由阿里云.百度智 ...
- Java规则引擎工作原理及其应用
摘 要 Java规则引擎是一种嵌入在Java程序中的组件,它的任务是把当前提交给引擎的Java数据对象与加载在引擎中的业务规则进行测试和比对,激活那些符合当前数据状态下的业务规则,根据业务规则中声明的 ...
- drools规则引擎的基本使用和原理介绍
理论基石 借用:<drools规则引擎技术指南>来说, drools是: 开源项目,规则引擎技术,规则语法形成的规则文件,可以存在数据库等,通过drools包提供的接口,调用生成对应的结果 ...
- 第2-4-8章 规则引擎Drools实战(1)-个人所得税计算器
文章目录 9. Drools实战 9.1 个人所得税计算器 9.1.1 名词解释 9.1.2 计算规则 9.1.2.1 新税制主要有哪些变化? 9.1.2.2 资较高人员本次个税较少,可能到年底扣税增 ...
- 大数据风控项目实战 Drools规则引擎
可以借鉴的干货 1,统一存储服务,包含:多种存储库连接封装和服务封装 在统一存储服务 2.获取配置的环境 类:EnvVariable 一.风控项目介绍 对一个复杂支付系统提供统一.全面.高效的风险控制 ...
最新文章
- 《学习OpenCV》第三章习题 第3题
- PTA数据结构与算法题目集(中文)7-12
- VTK:Utilities之Coordinate
- 设计类的五个原则_内容设计的5个原则
- Flask最强攻略 - 跟DragonFire学Flask - 第四篇 Flask 中的模板语言 Jinja2 及 render_template 的深度用法
- 获取access_token
- iOS -- 上传多张图片 后台(PHP)代码和上传一张的一样
- python sys模块 argv用法_python中sys模块的argv
- 虚拟服务器 vmotion,图文并茂:深入了解VMware vMotion过程
- 删除指定路径下的文件以及文件夹
- java实现排序的几种方法
- Android系统生成jks签名
- Python 竟然也可以写网页前端了!
- 流水线、超流水线、超标量(superscalar)技术对比
- android的sd卡分区,AndroidSD卡做磁盘分区图文教程
- TOMCAT的AppBase和DocBase研究
- 【面试】--【集合容器】
- Python | 一键生成九宫格图片
- 你不知道的 Canvas 表格交互
- Win10中使用CMD命令遍历文件夹删除重复文件
热门文章
- 21行代码AC——习题5-1 代码对齐(Alignment of Code, UVa1593)——解题报告
- 阻塞非阻塞和同步异步
- Mysql数据库(十一)——MHA高可用集群部署及故障切换
- 防火墙简介(二)——firewalld防火墙
- ACL初识(访问控制列表)
- FreeRTOS内核详解(1) —— 临界段保护原理
- c语言产生1-6,C语言 1-6小结.ppt
- am5718_基于TI AM5718 车牌识别系统解决方案 - 飞凌嵌入式行业资讯 - 保定飞凌嵌入式技术有限公司...
- python创建按钮_掌握Python之Tkinter按钮组件的创建及使用
- python绘制三维曲线图_Python基于matplotlib实现绘制三维图形功能示例