开发者

B站新一代 golang规则引擎gengine基础语法

开发者 https://www.devze.com 2023-12-12 10:46 出处:网络 作者: 萧楚河
目录前言优势语法DSL语法规则体语法规则体支持的运算规则体支持的基础数据类型不支持的特例规则体支持的语法使用案例示例解释小技巧前言编程客栈
目录
  • 前言
  • 优势
  • 语法
    • DSL语法
    • 规则体语法
      • 规则体支持的运算
      • 规则体支持的基础数据类型
      • 不支持的特例
    • 规则体支持的语法
    • 使用案例
      • 示例解释
        • 小技巧

        前言编程客栈

        • gengine是一款基于golang和AST(抽象语法树)开发的规则引擎,gengine支持的语法是一种自定义的DSL

        • gengine于2020年7月由哔哩哔哩(bilibili.com)授权开源

        • gengine现已应用于B站风控系统、流量投放系统、AB测试、推荐平台系统等多个业务场景

        • 你也可以将gengine应用于golang应用的任何需要规则或指标支持的业务场景

        优势

        对比droolsgengine
        执行模式仅支持顺序模式支持顺序模式、并发模式、混合模式,以及其他细分执行模式
        规则编写难易程度高,与Java强相关低,自定义简单语法,与golang弱相关
        规则执行性能低、无论是规则之间还是规则内部,都是顺序执行高,无论是规则间、还是规则内,都支持并发执行.用户基于需要来选择合适的执行模式

        开源代码地址

        https://github.com/bilibili/gengine

        https://github.com/bilibili/gengine

        语法

        DSL语法

        const rule = `
        rule "rulename" "rule-describtion" salience  10
        begin
        //规则体
        end`

        如上,gengine DSL完整的语法块由如下几个组件构成:

        • 关键字rule,之后紧跟"规则名称"和"规则描述",规则名称是必须的,但规则描述不是必须的. 当一个gengine实例中有多个规则时,"规则名"必须唯一,否则当有多个相同规则名的规则时,编译好之后只会存在一个

        • 关键字salience,之后紧跟一个整数,表示的规则优先级,它们为非必须.数字越大,规则优先级越高;当用户没有显式的指明优先级时,规则的优先级未知, 如果多个规则的优先级相同,那么在执行的时候,相同优先级的规则执行顺序未知

        • 关键字begin和end包裹的是规则体,也就是规则的具体逻辑

        规则体语法

        • 规则体的语法支持或执行顺序,与主流的计算计语言(如golang、java、C/C++等)一致

        规则体支持的运算

        • 支持完整数值之间的加(+)、减(-)、乘(*)、除(/)四则运算,以及字符串之间的加法

        • 完整的逻辑运算(&&、 ||、 !)

        • 支持比较运算符: 等于(==)、不等于(!=)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)

        • 支持+=, -=, *=, /=

        • 支持小括号

        • 优先级:括号, 非, 乘除, 加减, 逻辑运算(&&,||) 依次降低

        规则体支持的基础数据类型

        • string

        • bool

        • int, int8, int16, int32, int64

        • uint, uint8, uint16,uint32, uint64

        • float32, float64

        不支持的特例

        • 不支持直接处理nil,但用户可以在rule中定义一个变量去接受nil,然后再定义一个函数去处理nil

        • 为了用户使用方便,最新版gengine已经内置了isNil()函数,用户可以直接使用,用于判断数据是否为nil

        规则体支持的语法

        • 完整的if .. else if .. else 语法结构,及其嵌套结构

        其他语法大家如有需求可以去官方文档查看:

        https://github.com/bilibili/gengine/wiki/%E8%AF%AD%E6%B3%95

        使用案例

        package test
        import (
          "bytes"
          "fmt"
          "github.com/bilibili/gengine/builder"
          "github.com/bilibili/gengine/context"
          "github.com/bilibili/gengine/engine"
          "github.com/sirupsen/logruzgjZuNs"
          "io/ioutil"
          "strconv"
          "strings"
          "testing"
          "time"
        )
        //定义想要注入的结构体
        type User struct {
          Name string
          Age  int64
          Male bool
        }
        func (u *User)GetNum(i int64) int64 {
          return i
        }
        func (u *User)Print(s string){
          fmt.Println(s)
        }
        func (u *User)Say(){
          fmt.Println("hello world")
        }
        //定义规则
        const rule1 = `
        rule "name test" "i can"  salience 0
        begin
            if 7 == User.GetNum(7){
              User.Age = User.GetNum(89767) + 10000000
              User.Print("6666")
            }else{
              User.Name = "yyyy"
            }
        end
        `
        func Test_Multi(t *testing.T){
          user := &User{
            Name: "Calo",
            Age:  0,
            Male: true,
          }
          dataContext := context.NewDataContext()
          //注入初始化的结构体
          dataContext.Add("User", user)
          //init rule engine
          ruleBuilder := builder.NewRuleBuilder(dataContext)
          start1 := time.Now().UnixNano()
            //构建规则
          err := ruleBuilder.BuildRuleFromString(rule1) //string(bs)
          end1 := time.Now().UnixNano()
          logrus.Infof("rules num:%d, load rules cost time:%d", len(ruleBuilder.Kc.RuleEntities), end1-start1 )
          if err != niphpl{
            logrus.Errorf("err:%s ", err)
          }else{
            eng := engine.NewGengine()
            start := time.Now().UnixNano()
                //执行规则
            err := eng.Execute(ruleBuilder,true)
            println(user.Age)
            end := time.Now().UnixNano()
            if err != nil{
              logrus.Errorf("execute rule error: %v", err)
            }
            logrus.Infofjs("execute rule cost %d ns",end-start)
            logrus.Infof("user.Age=%d,Name=%s,Male=%t", user.Age, user.Name, user.Male)
          }
        }

        示例解释

        • User是需要被注入到gengine中的结构体;结构体在注入之前需要被初始化;结构体需要以指针的形式被注入,否则无法在规则中改变其属性值

        • rule1是以字符串定义的具体规则

        • dataContext用于接受注入的数据(结构体、方法等)

        • ruleBuilder用于编译字符串形式的规则

        • engine接受ruleBuilder,并以用户选定的执行模式执行加载好的规则

        小技巧

        • 通过示例可以发现,规则的zgjZuN编译构建和执行是异步的. 因此,用户可使用此特性,在不停服的情况下进行更新规则.

        • 需要注意的是,编译构建规则是一个CPU密集型的事情,通常只有规则被用户更新的时候才去编译构建更新;

        • gengine内部针对规则加载与移除已经做了很多的优化,gengine pool提供的所有相关的api也是线程安全且高性能的,因此我们建议您直接使用gengine pool

        • 另外,用户还可以通过ruleBuilder来进行异步的语法检测.

        如果大家对设计实现比较感兴趣可以通过如下地址查看:

        //www.jb51.net/jiaoben/284935hzu.htm

        以上就是B站新一代 golang规则引擎gengine基础语法的详细内容,更多关于B站go规则引擎gengine的资料请关注编程客栈(www.devze.com)其它相关文章!

        0

        精彩评论

        暂无评论...
        验证码 换一张
        取 消