type
status
date
slug
summary
tags
category
icon
password
这篇文章是 gengine 的简单使用介绍,内容大部分来自于 gengine 的文档,只是做了一个简单的整合。
📝快速入门
gengine简介
gengine
是一款基于 golang 和 AST (抽象语法树)开发的规则引擎,gengine
支持的语法是一种自定义的 DSL
gengine
于2020年7月由哔哩哔哩 (bilibili.com
)授权开源
gengine
现已应用于 B 站风控系统、流量投放系统、AB 测试、推荐平台系统等多个业务场景
- 你也可以将
gengine
应用于golang
应用的任何需要规则或指标支持的业务场景
相似工具对比
ㅤ | gengine | gopher-lua |
执行模式 | ㅤ | ㅤ |
规则便携难度 | ㅤ | ㅤ |
功能 | 功能较少,适合简单的逻辑处理 | 支持 lua 语法,功能强大 |
性能 | ㅤ | ㅤ |
github stars | ㅤ | ㅤ |
代码更新频率 | ㅤ | ㅤ |
文档 |
安装
一个例子
示例解释
User
是需要被注入到gengine
中的结构体;结构体在注入之前需要被初始化;结构体需要以指针的形式被注入, 否则无法在规则中改变其属性值
compare_user_rule1
是以字符串定义的具体规则
dataContext
用于接受注入的数据 (结构体、方法等)
ruleBuilder
用于编译字符串形式的规则
engine
接受ruleBuilder
, 并以用户选定的执行模式执行加载好的规则
GetRulesResultMap()
返回compare_user_rule
1 中每个规则的结果
语法
gengine DSL语法
如上,gengine DSL完整的语法块由如下几个组件构成:
- 关键字rule,之后紧跟"规则名称"和"规则描述",规则名称是必须的,但规则描述不是必须的. 当一个gengine实例中有多个规则时,"规则名"必须唯一,否则当有多个相同规则名的规则时,编译好之后只会存在一个
- 关键字
salience
, 之后紧跟一个整数, 表示的规则优先级, 它们为非必须. - 数字越大, 规则优先级越高;
- 当用户没有显式的指明优先级时, 规则的优先级未知, 如果多个规则的优先级相同,那么在执行的时候,相同优先级的规则执行顺序未知
- 关键字
begin
和 end
注释
- 支持规则内的单行注释,注释以双斜杠(//)开头
支持的数据类型
- string
- bool
- int, int8, int16, int32, int64
- uint, uint8, uint16, uint32, uint64
- float32, float64
数据结构
gengine主要支持4种数据结构,分别是golang的struct,以及基础类型的map、array和slice, 为了保持简洁、以及语言的无关性, 支持这4种结构的如下操作:
struct
注入struct必须是以指针的形式注入,否则无法在规则中改变结构体的属性值,注入结构体示例如下:
map
gengine
只能基于 key
对 map
取值或设置值。数组
gengine
只能基于 index
对数组取值或设置值, 且注入的数组, 必须为指针数组, 或者附着与结构体指针的数组, 当以非指针形式的数组注入 dataContext
. 则只能用 index 取值, 而不能设置值slice
nil
不支持 nil,如果需要,可以使用内置函数
isNil()
代替isNil
的参数只有一个, 且可以是任意类型的; 其返回值是true
或false
- 当你有不确定其返回值是否为
nil
, 而导致规则不可正常执行时, 都可以使用isNil
函数来判别一下
- 基础类型永远都不是 nil, 都是其对应的默认值
- 非基础类型可能为nil,具体测试详见如下
支持的运算
- 支持完整数值之间的加
(+)
、减(-)
、乘(*)
、除(/)
四则运算, 以及字符串之间的加法
- 完整的逻辑运算
(&&、 ||、 !)
- 支持比较运算符: 等于
(==)
、不等于(!=)
、大于(>)
、小于(<)
、大于等于(>=)
、小于等于(<=)
- 支持
+=, -=, *=, /=
- 支持小括号
- 优先级:括号, 非, 乘除, 加减, 逻辑运算
(&&,||)
依次降低
变量
- 用户自定义变量无需申明类型
- 规则内定义的变量,只对当前规则可见,对其他规则不可见(局部变量)
- 使用dataContext注入的(变量)数据,对加载到gengine中的所有规则均可见(全局变量)
变量声明
使用规则外变量
rule 特殊变量
@name
:- 在规则体中使用@name,指代的是当前规则名,@name在规则执行时,会被解析为规则名字符串(规则内的名字感知)
@id
:- 在规则体中使用@id含义是,如果当前的规则名是可以转化为整数的字符串,则@id就是规则名的整数值,如果规则名字符串不可转化为整数,则@id的值为0.这个是为了方便用户以规则名作为整型参数
@desc
:- 在规则体内获知当前规则的描述信息,类型为字符串
@sal
:- 在规则体内获知当前规则的优先级信息,类型为int64
控制流
if ... else
for
range number
Range map
Range slice
函数
gengine 不支持直接在 rule 中写函数,在 rule 中使用的函数需要在规则外声明,然后注入到 rule 中,
为了尽量保证规则的书写和golang语法的无关性,规则体内可以调用具有多返回的函数或方法,但当使用自定义变量接受函数、方法返回值时,只支持但返回一个返回数据的函数和方法调用(也是间接建议用户为自己的业务实现设置适当的默认值,而不是直接报错)
内置函数
- 为了处理 golang 语言的 nil, 简化用户使用, gengine 目前提供了一个唯一的内置函数 isNil ()
- isNil的参数只有一个,且可以是任意类型的;其返回值是true或false
- 当你有不确定其返回值是否为nil,而导致规则不可正常执行时,都可以使用isNil函数来判别一下
使用方法参考数据类型
nil
部分。并发
规则内并发
- 在很多业务场景下, 一个规则, 需要很多的数据指标支持, 因为数据量的缘故, 这些数据指标往往不可能存储在本地.
- 因此,如果在规则内顺序地从网络上取很多个指标时,会严重拖慢规则的执行性能.
- 基于此考量,gengine 支持规则内的并发函数调用或赋值语句. 其语法相当简单, 使用 conc{}语句块来包裹需要并发调用的接口即可.
- conc{}语句块内支持放置方法、函数、赋值语句
- conc{}语句块内可以为空,或有一个或多个方法、函数或赋值语句
- conc{}语句块为空时,不产生任何影响,当仅有一条语句时,退化为顺序执行,当多于2条或2条以上的语句时,会进行并发执行
- 用户需要自己来保证在conc{}中的调用的api是线程安全的
- 多执行几次测试,你会发现conc{}内的输出顺序每次都不一样
实践
BuildRuleFromString 是一个耗时操作,不建议每次都创建一个新的 gengine。
📎 参考文章
- 作者:Gusibi
- 链接:https://blog.gusibi.mobi/article/bilibili-gengine-guide
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章