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。

📎 参考文章

 
避免这些错误:Vercel 部署 Hugo 的最佳实践Flutter Markdown - How to use markdown in Flutter
Gusibi
Gusibi
后端开发,擅长使用 Python 和 Golang,现在面向AI编程。热爱探索新互联网产品,曾写过小程序。正在学习 Flutter,热衷于技术的探索与进步。
公告
type
status
date
slug
summary
tags
category
icon
password
🎉欢迎访问🎉
-- 感谢您的支持 ---
👏欢迎更新体验👏
 
🤺
关于我