B站新一代 golang規(guī)則引擎gengine基礎(chǔ)語(yǔ)法
前言
gengine是一款基于golang和AST(抽象語(yǔ)法樹(shù))開(kāi)發(fā)的規(guī)則引擎,gengine支持的語(yǔ)法是一種自定義的DSL
gengine于2020年7月由嗶哩嗶哩(bilibili.com)授權(quán)開(kāi)源
gengine現(xiàn)已應(yīng)用于B站風(fēng)控系統(tǒng)、流量投放系統(tǒng)、AB測(cè)試、推薦平臺(tái)系統(tǒng)等多個(gè)業(yè)務(wù)場(chǎng)景
你也可以將gengine應(yīng)用于golang應(yīng)用的任何需要規(guī)則或指標(biāo)支持的業(yè)務(wù)場(chǎng)景
優(yōu)勢(shì)
對(duì)比 | drools | gengine |
---|---|---|
執(zhí)行模式 | 僅支持順序模式 | 支持順序模式、并發(fā)模式、混合模式,以及其他細(xì)分執(zhí)行模式 |
規(guī)則編寫(xiě)難易程度 | 高,與java強(qiáng)相關(guān) | 低,自定義簡(jiǎn)單語(yǔ)法,與golang弱相關(guān) |
規(guī)則執(zhí)行性能 | 低、無(wú)論是規(guī)則之間還是規(guī)則內(nèi)部,都是順序執(zhí)行 | 高,無(wú)論是規(guī)則間、還是規(guī)則內(nèi),都支持并發(fā)執(zhí)行.用戶基于需要來(lái)選擇合適的執(zhí)行模式 |
開(kāi)源代碼地址
https://github.com/bilibili/gengine
https://github.com/bilibili/gengine
語(yǔ)法
DSL語(yǔ)法
const rule = ` rule "rulename" "rule-describtion" salience 10 begin //規(guī)則體 end`
如上,gengine DSL完整的語(yǔ)法塊由如下幾個(gè)組件構(gòu)成:
關(guān)鍵字rule,之后緊跟"規(guī)則名稱(chēng)"和"規(guī)則描述",規(guī)則名稱(chēng)是必須的,但規(guī)則描述不是必須的. 當(dāng)一個(gè)gengine實(shí)例中有多個(gè)規(guī)則時(shí),"規(guī)則名"必須唯一,否則當(dāng)有多個(gè)相同規(guī)則名的規(guī)則時(shí),編譯好之后只會(huì)存在一個(gè)
關(guān)鍵字salience,之后緊跟一個(gè)整數(shù),表示的規(guī)則優(yōu)先級(jí),它們?yōu)榉潜仨?數(shù)字越大,規(guī)則優(yōu)先級(jí)越高;當(dāng)用戶沒(méi)有顯式的指明優(yōu)先級(jí)時(shí),規(guī)則的優(yōu)先級(jí)未知, 如果多個(gè)規(guī)則的優(yōu)先級(jí)相同,那么在執(zhí)行的時(shí)候,相同優(yōu)先級(jí)的規(guī)則執(zhí)行順序未知
關(guān)鍵字begin和end包裹的是規(guī)則體,也就是規(guī)則的具體邏輯
規(guī)則體語(yǔ)法
規(guī)則體的語(yǔ)法支持或執(zhí)行順序,與主流的計(jì)算計(jì)語(yǔ)言(如golang、java、C/C++等)一致
規(guī)則體支持的運(yùn)算
支持完整數(shù)值之間的加(+)、減(-)、乘(*)、除(/)四則運(yùn)算,以及字符串之間的加法
完整的邏輯運(yùn)算(&&、 ||、 !)
支持比較運(yùn)算符: 等于(==)、不等于(!=)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)
支持+=, -=, *=, /=
支持小括號(hào)
優(yōu)先級(jí):括號(hào), 非, 乘除, 加減, 邏輯運(yùn)算(&&,||) 依次降低
規(guī)則體支持的基礎(chǔ)數(shù)據(jù)類(lèi)型
string
bool
int, int8, int16, int32, int64
uint, uint8, uint16,uint32, uint64
float32, float64
不支持的特例
不支持直接處理nil,但用戶可以在rule中定義一個(gè)變量去接受nil,然后再定義一個(gè)函數(shù)去處理nil
為了用戶使用方便,最新版gengine已經(jīng)內(nèi)置了isNil()函數(shù),用戶可以直接使用,用于判斷數(shù)據(jù)是否為nil
規(guī)則體支持的語(yǔ)法
完整的if .. else if .. else 語(yǔ)法結(jié)構(gòu),及其嵌套結(jié)構(gòu)
其他語(yǔ)法大家如有需求可以去官方文檔查看:
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/logrus" "io/ioutil" "strconv" "strings" "testing" "time" ) //定義想要注入的結(jié)構(gòu)體 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") } //定義規(guī)則 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() //注入初始化的結(jié)構(gòu)體 dataContext.Add("User", user) //init rule engine ruleBuilder := builder.NewRuleBuilder(dataContext) start1 := time.Now().UnixNano() //構(gòu)建規(guī)則 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 != nil{ logrus.Errorf("err:%s ", err) }else{ eng := engine.NewGengine() start := time.Now().UnixNano() //執(zhí)行規(guī)則 err := eng.Execute(ruleBuilder,true) println(user.Age) end := time.Now().UnixNano() if err != nil{ logrus.Errorf("execute rule error: %v", err) } logrus.Infof("execute rule cost %d ns",end-start) logrus.Infof("user.Age=%d,Name=%s,Male=%t", user.Age, user.Name, user.Male) } }
示例解釋
User是需要被注入到gengine中的結(jié)構(gòu)體;結(jié)構(gòu)體在注入之前需要被初始化;結(jié)構(gòu)體需要以指針的形式被注入,否則無(wú)法在規(guī)則中改變其屬性值
rule1是以字符串定義的具體規(guī)則
dataContext用于接受注入的數(shù)據(jù)(結(jié)構(gòu)體、方法等)
ruleBuilder用于編譯字符串形式的規(guī)則
engine接受ruleBuilder,并以用戶選定的執(zhí)行模式執(zhí)行加載好的規(guī)則
小技巧
通過(guò)示例可以發(fā)現(xiàn),規(guī)則的編譯構(gòu)建和執(zhí)行是異步的. 因此,用戶可使用此特性,在不停服的情況下進(jìn)行更新規(guī)則.
需要注意的是,編譯構(gòu)建規(guī)則是一個(gè)CPU密集型的事情,通常只有規(guī)則被用戶更新的時(shí)候才去編譯構(gòu)建更新;
gengine內(nèi)部針對(duì)規(guī)則加載與移除已經(jīng)做了很多的優(yōu)化,gengine pool提供的所有相關(guān)的api也是線程安全且高性能的,因此我們建議您直接使用gengine pool
另外,用戶還可以通過(guò)ruleBuilder來(lái)進(jìn)行異步的語(yǔ)法檢測(cè).
如果大家對(duì)設(shè)計(jì)實(shí)現(xiàn)比較感興趣可以通過(guò)如下地址查看:
//www.dbjr.com.cn/jiaoben/284935hzu.htm
以上就是B站新一代 golang規(guī)則引擎gengine基礎(chǔ)語(yǔ)法的詳細(xì)內(nèi)容,更多關(guān)于B站go規(guī)則引擎gengine的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言常見(jiàn)設(shè)計(jì)模式之裝飾模式詳解
在?Go?語(yǔ)言中,雖然裝飾模式?jīng)]有像?Python?中應(yīng)用那么廣泛,但也有其用武之地,這篇文章我們就來(lái)一起看下裝飾模式在?Go?語(yǔ)言中的應(yīng)用吧2023-07-07Go語(yǔ)言開(kāi)發(fā)中有了net/http為什么還要有g(shù)in的原理及使用場(chǎng)景解析
這篇文章主要為大家介紹了Go語(yǔ)言有了net/http標(biāo)準(zhǔn)庫(kù)為什么還要有g(shù)in第三方庫(kù)的原理及使用場(chǎng)景詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Go語(yǔ)言開(kāi)發(fā)中redis的使用詳解
這篇文章主要介紹了Go語(yǔ)言開(kāi)發(fā)中redis的使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07詳解Go操作supervisor xml rpc接口及注意事項(xiàng)
這篇文章主要介紹了Go操作supervisor xml rpc接口及注意事項(xiàng),管理web,在配置文件中配置相關(guān)信息,通過(guò)go-supervisor的處理庫(kù)進(jìn)行操作,需要的朋友可以參考下2021-09-09Golang開(kāi)發(fā)中如何解決共享變量問(wèn)題
Go提供了傳統(tǒng)通過(guò)共享變量,也就是共享內(nèi)存的方式來(lái)實(shí)現(xiàn)并發(fā)。這篇文章會(huì)介紹 Go提供的相關(guān)機(jī)制,對(duì)Golang共享變量相關(guān)知識(shí)感興趣的朋友一起看看吧2021-09-09golang獲取prometheus數(shù)據(jù)(prometheus/client_golang包)
本文主要介紹了使用Go語(yǔ)言的prometheus/client_golang包來(lái)獲取Prometheus監(jiān)控?cái)?shù)據(jù),具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03Go實(shí)現(xiàn)自動(dòng)解壓縮包以及讀取docx/doc文件內(nèi)容詳解
在開(kāi)發(fā)過(guò)程中,我們常常需要處理壓縮包和文檔文件。本文將介紹如何使用Go語(yǔ)言自動(dòng)解壓縮包和讀取docx/doc文件,需要的可以參考一下2023-03-03