Gengine规则引擎
最近對B站開源的gengine規則引擎進行了入門級的研究。現在整理的資料記錄如下。(歡迎交流討論)
原博客地址:?https://www.cnblogs.com/feixiang-energy/p/15572292.html
一:簡介:
Gengine是一款基于golang和AST(抽象語法樹)開發的規則引擎, Gengine支持的語法是一種自定義的DSL, Gengine通過內置的解釋器對規則文件進行解析,構建規則模型,進行相應的規則計算和數據處理。Gengine于2020年7月由嗶哩嗶哩(bilibili.com)授權開源。Gengine現已應用于B站風控系統、流量投放系統、AB測試、推薦平臺系統等多個業務場景。
官網上給出的Gengine相比于Java領域的著名規則引擎drools優勢如下:
| 對比 | drools | gengine |
| 執行模式 | 僅支持順序模式 | 支持順序模式、并發模式、混合模式,以及其他細分執行模式 |
| 規則編寫難易程度 | 高,與java強相關 | 低,自定義簡單語法,與golang弱相關 |
| 規則執行性能 | 低、無論是規則之間還是規則內部,都是順序執行 | 高,無論是規則間、還是規則內,都支持并發執行.用戶基于需要來選擇合適的執行模式 |
Gengine開源地址:GitHub - bilibili/gengine
二:環境準備:
Go語言環境準備:
開發工具準備:
第三方庫準備:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package?main //庫引用 import?( ???"fmt" ???"github.com/bilibili/gengine/builder" ???"github.com/bilibili/gengine/context" ???"github.com/bilibili/gengine/engine" ) //定義規則 (通過函數注入的方式,打印"hello world") const?rule = ` rule?"1"?"rule-des"?salience 10 begin println("hello world, gengine!") end ` //主函數 func?main(){ ???//初始化數據環境變量 ???dataContext := context.NewDataContext() ???//注入println函數 ???dataContext.Add("println",fmt.Println) ???//初始化規則 ???ruleBuilder := builder.NewRuleBuilder(dataContext) ???//讀取規則 ???err1 := ruleBuilder.BuildRuleFromString(rule) ???fmt.Println(err1) ???//初始化規則引擎 ???eng := engine.NewGengine() ???//執行規則引擎 ???err2 := eng.Execute(ruleBuilder,true) ???fmt.Println(err2) } |
三:功能簡介:
支持的規則語法:
- 邏輯運算:&&、||、!、==、!=、>、>=、<、<=等。
- 四則運算:+、-、*、/、()等。
- If else條件選擇。
- 預加載API。
規則文件:
- 支持規則名稱、描述、優先級設置。
- 支持規則注釋。
- 支持@name、@id、@desc獲取規則信息。
- 支持自定義變量。
- 支持報錯時行號提示。
- 支持規則內調用注入的函數。
- 支持規則內conc{}語句塊并發執行。
- 目前不支持web可視化編寫規則文件,還需要技術人員進行手動配置。
執行模式:
- 順序模式:當指定規則優先級時,按照優先級順序執行。
- 并發模式:不考慮優先級、各個規則并發執行。
- 混合模式:先執行優先級最高的一個,剩余的n-1個并發執行。
- 逆混合模式:先并發執行優先級最高的n-1個,都執行結束后執行最后一個。
對外API接口:
- dataContext:支持注入需要在規則中使用的結構體、函數。
- ruleBuilder:與dataContext關聯,支持通過字符串方式導入規則。
- engine:創建規則引擎,執行ruleBuilder關聯的規則。
- GenginePoll:引擎實例池,支持在高QPS下實現高并發和線程安全。
支持的規則注入:
- golang的struct結構體。(以指針方式注入)
- 基礎類的map、array、slice。
- Golang編寫的函數。
支持引擎池:
- 類似于線程池或數據庫連接池。
四:實驗驗證:
單規則:
一個比較全的單規則例子:
驗證了:結構體注入、函數注入、加法運算、自定義變量、結構體變量修改。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package?main import?( ???"fmt" ???"github.com/bilibili/gengine/engine" ???"github.com/bilibili/gengine/engine" ???"github.com/bilibili/gengine/engine" ???"strconv" ) type?User?struct?{ ???Name string ???Age int64 ???Male bool } func?(u *User) SayHi(s string){ ???fmt.Println("Hi "?+ s +?", I am "?+ u.Name) } func?PrintAge(age int64)? { ???fmt.Println("Age is "?+ strconv.FormatInt(age, 10)) } const?( ???rule1 = ` rule?"rule1"?"a test"?salience 10 begin ???println(@name) ???user.SayHi("lily") ???if?user.Age > 20{ ??????newAge = user.Age + 100 ??????user.Age = newAge ???} ???PrintAge(user.Age) ???user.Male = false end ` ) func?main(){ ???dataContext := context.NewDataContext() ???user := &User{ ??????Name:?"Calo", ??????Age:? 25, ??????Male: true, ???} ???dataContext.Add("user",user) ???dataContext.Add("println",fmt.Println) ???dataContext.Add("PrintAge", PrintAge) ???ruleBuilder := builder.NewRuleBuilder(dataContext) ???err1 := ruleBuilder.BuildRuleFromString(rule1) ???if?err1 != nil { ??????panic(err1) ???} ???eng := engine.NewGengine() ???err2 := eng.Execute(ruleBuilder,true) ???if?err2 != nil { ??????panic(err2) ???} ???fmt.Printf("Age=%d Name=%s,Male=%t", user.Age, user.Name, user.Male) |
順序執行:
一個多規則順序執行的例子:
模擬探測站總貌狀態共3個規則:正常、預警、異常。順序執行。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | package?main import?( ???"fmt" ???"github.com/bilibili/gengine/engine" ???"github.com/bilibili/gengine/engine" ???"github.com/bilibili/gengine/engine" ) type?Station?struct?{ ???Temperature int64?????//溫度 ???Humidity int64???????//濕度 ???Water int64?????????????//水浸 ???Smoke int64?????????????//煙霧 ???Door1 int64?????????????//門禁1 ???Door2 int64?????????????//門禁2 ???StationState int64????//探測站狀態:?? 0正常;1預警;2異常;3未知 } const?( ???stateRule = ` rule?"normalRule"?"探測站狀態正常計算規則"?salience 8 begin ???println("/***************** 正常規則 ***************") ???if?Station.Temperature>0 && Station.Temperature<80 && Station.Humidity<70 && Station.Water==0 && Station.Smoke==0 && Station.Door1==0 && Station.Door2==0{ ??????Station.StationState=0 ??????println("滿足") ???}else{ ??????println("不滿足") ???} end rule?"errorRule"?"探測站狀態預警計算規則"?salience 9 begin ???println("/***************** 預警規則 ***************") ???if?Station.Temperature>0 && Station.Temperature<80 && Station.Humidity<70 && Station.Water==0 && Station.Smoke==0 && (Station.Door1==1 || Station.Door2==1){ ??????Station.StationState=1 ??????println("滿足") ???}else{ ??????println("不滿足") ???} end rule?"warnRule"?"探測站狀態異常計算規則"?salience 10 begin ???println("/***************** 異常規則 ***************") ???if?Station.Temperature<0 || Station.Temperature>80 || Station.Humidity>70 || Station.Water==1 || Station.Smoke==1{ ??????Station.StationState=2 ??????println("滿足") ???}else{ ??????println("不滿足") ???} end ` ) func?main(){ ???station := &Station{ ??????Temperature: 40, ??????Humidity:? 30, ??????Water: 0, ??????Smoke: 1, ??????Door1: 0, ??????Door2: 1, ??????StationState: 0, ???} ???dataContext := context.NewDataContext() ???dataContext.Add("Station", station) ???dataContext.Add("println",fmt.Println) ???ruleBuilder := builder.NewRuleBuilder(dataContext) ???err1 := ruleBuilder.BuildRuleFromString(stateRule) ???if?err1 != nil { ??????panic(err1) ???} ???eng := engine.NewGengine() ???err2 := eng.Execute(ruleBuilder, true) ???if?err2 != nil { ??????panic(err2) ???} ???fmt.Printf("StationState=%d", station.StationState) } |
并發執行:
一個多規則并發執行的例子:
模擬探測站報警事件共3個規則:溫度報警、水浸報警、煙霧報警。并發執行。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | package?main import?( ???"fmt" ???"github.com/bilibili/gengine/engine" ???"github.com/bilibili/gengine/engine" ???"github.com/bilibili/gengine/engine" ) type?Temperature?struct?{ ???Tag string?????????????//標簽點名稱 ???Value float64???????????//數據值 ???State int64????????????????//狀態 ???Event string????????????//報警事件 } type?Water?struct?{ ???Tag string?????????????//標簽點名稱 ???Value int64?????????????//數據值 ???State int64????????????????//狀態 ???Event string????????????//報警事件 } type?Smoke?struct?{ ???Tag string?????????????//標簽點名稱 ???Value int64?????????????//數據值 ???State int64????????????????//狀態 ???Event string????????????//報警事件 } const?( ???eventRule = ` rule?"TemperatureRule"?"溫度事件計算規則" begin ???println("/***************** 溫度事件計算規則 ***************/") ???tempState = 0 ???if?Temperature.Value < 0{ ??????tempState = 1 ???}else?if?Temperature.Value > 80{ ??????tempState = 2 ???} ???if?Temperature.State != tempState{ ??????if?tempState == 0{ ?????????Temperature.Event =?"溫度正常" ??????}else?if?tempState == 1{ ?????????Temperature.Event =?"低溫報警" ??????}else{ ?????????Temperature.Event =?"高溫報警" ??????} ???}else{ ??????Temperature.Event =?"" ???} ???Temperature.State = tempState end rule?"WaterRule"?"水浸事件計算規則" begin ???println("/***************** 水浸事件計算規則 ***************/") ???tempState = 0 ???if?Water.Value != 0{ ??????tempState = 1 ???} ???if?Water.State != tempState{ ??????if?tempState == 0{ ?????????Water.Event =?"水浸正常" ??????}else{ ?????????Water.Event =?"水浸異常" ??????} ???}else{ ??????Water.Event =?"" ???} ???Water.State = tempState end rule?"SmokeRule"?"煙霧事件計算規則" begin ???println("/***************** 煙霧事件計算規則 ***************/") ???tempState = 0 ???if?Smoke.Value != 0{ ??????tempState = 1 ???} ???if?Smoke.State != tempState{ ??????if?tempState == 0{ ?????????Smoke.Event =?"煙霧正常" ??????}else{ ?????????Smoke.Event =?"煙霧報警" ??????} ???}else{ ??????Smoke.Event =?"" ???} ???Smoke.State = tempState end `) func?main(){ ???temperature := &Temperature{ ??????Tag:?"temperature", ??????Value:? 90, ??????State: 0, ??????Event:?"", ???} ???water := &Water{ ??????Tag:?"water", ??????Value:? 0, ??????State: 0, ??????Event:?"", ???} ???smoke := &Smoke{ ??????Tag:?"smoke", ??????Value:? 1, ??????State: 0, ??????Event:?"", ???} ???dataContext := context.NewDataContext() ???dataContext.Add("Temperature", temperature) ???dataContext.Add("Water", water) ???dataContext.Add("Smoke", smoke) ???dataContext.Add("println",fmt.Println) ???ruleBuilder := builder.NewRuleBuilder(dataContext) ???err1 := ruleBuilder.BuildRuleFromString(eventRule) ???if?err1 != nil { ??????panic(err1) ???} ???eng := engine.NewGengine() ???eng.ExecuteConcurrent(ruleBuilder) ???fmt.Printf("temperature Event=%s\n", temperature.Event) ???fmt.Printf("water Event=%s\n", water.Event) ???fmt.Printf("smoke Event=%s\n", smoke.Event) ???for?i := 0; i < 10; i++ { ??????smoke.Value = int64(i % 3) ??????eng.ExecuteConcurrent(ruleBuilder) ??????fmt.Printf("smoke Event=%s\n", smoke.Event) ???} } |
引擎池:
一個引擎池的例子:
創建了一個最大3個實例的引擎池。并發執行5個計算引擎。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | package?main import?( ???"fmt" ???"github.com/bilibili/gengine/engine" ???"math/rand" ???"sync/atomic" ???"time" ) const?rulePool = ` rule?"rulePool"?"rule-des"?salience 10 begin sleep() //print("do ", FunParam.Name) end ` type?FunParam?struct?{ ???Name string } func?Sleep() { ???rand.Seed(time.Now().UnixNano()) ???i := rand.Intn(1000) ???time.Sleep(time.Nanosecond * time.Duration(i)) } func?main(){ ???Sleep() ???apis := make(map[string]interface{}) ???apis["print"] = fmt.Println ???apis["sleep"] = Sleep ???pool, e1 := engine.NewGenginePool(1, 3, 2, rulePool, apis) ???if?e1 != nil { ??????panic(e1) ???} ???g1 := int64(0) ???g2 := int64(0) ???g3 := int64(0) ???g4 := int64(0) ???g5 := int64(0) ???cnt := int64(0) ???go?func() { ??????for?{ ?????????param := &FunParam{Name:?"func1"} ?????????e2 := pool.ExecuteRules("FunParam", param,?"", nil) ?????????if?e2 != nil { ????????????println(fmt.Sprintf("e2: %+v", e2)) ?????????} ?????????//time.Sleep(1 * time.Second) ?????????atomic.AddInt64(&cnt, 1) ?????????g1++ ??????} ???}() ???go?func() { ??????for?{ ?????????param := &FunParam{Name:?"func2"} ?????????e2 := pool.ExecuteRules("FunParam", param,?"", nil) ?????????if?e2 != nil { ????????????println(fmt.Sprintf("e2: %+v", e2)) ?????????} ?????????//time.Sleep(1 * time.Second) ?????????atomic.AddInt64(&cnt, 1) ?????????g2++ ??????} ???}() ???go?func() { ??????for?{ ?????????param := &FunParam{Name:?"func3"} ?????????e2 := pool.ExecuteRules("FunParam", param,?"", nil) ?????????if?e2 != nil { ????????????println(fmt.Sprintf("e2: %+v", e2)) ?????????} ?????????//time.Sleep(1 * time.Second) ?????????atomic.AddInt64(&cnt, 1) ?????????g3++ ??????} ???}() ???go?func() { ??????for?{ ?????????param := &FunParam{Name:?"func4"} ?????????e2 := pool.ExecuteRules("FunParam", param,?"", nil) ?????????if?e2 != nil { ????????????println(fmt.Sprintf("e2: %+v", e2)) ?????????} ?????????//time.Sleep(1 * time.Second) ?????????atomic.AddInt64(&cnt, 1) ?????????g4++ ??????} ???}() ???go?func() { ??????for?{ ?????????param := &FunParam{Name:?"func5"} ?????????e2 := pool.ExecuteRules("FunParam", param,?"", nil) ?????????if?e2 != nil { ????????????println(fmt.Sprintf("e2: %+v", e2)) ?????????} ?????????//time.Sleep(1 * time.Second) ?????????atomic.AddInt64(&cnt, 1) ?????????g5++ ??????} ???}() ???// 主進程運行5秒 ???time.Sleep(5 * time.Second) ???// 統計各個子進程分別運行次數 ???println(g1, g2, g3, g4, g5) ???// 統計在引擎池下總的各個子進程總的運行測試 ???println(g1 + g2 + g3 + g4 + g5, cnt) } |
規則文件熱更新:
一個單例引擎增量更新規則文件的例子:
驗證了在不中斷引擎計算的情況下:1)更新指定名稱的規則配置;2)添加規則配置。
規則文件還支持動態刪除、引擎池熱更新等操作。不再驗證。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | package?main import?( ???"fmt" ???"github.com/bilibili/gengine/builder" ???"github.com/bilibili/gengine/context" ???"github.com/bilibili/gengine/engine" ???"math/rand" ???"strconv" ???"time" ) type?Student?struct?{ ???Name string????????????????//姓名 ???score int64????????????????//分數 } const?( ???ruleInit = ` rule?"ruleScore"?"rule-des"?salience 10 ???begin ???if?Student.score > 60 { ??????println(Student.Name, FormatInt(Student.score, 10),?"及格") ???}else{ ??????println(Student.Name, FormatInt(Student.score, 10),?"不及格") ???} end ` ???ruleUpdate = ` rule?"ruleScore"?"rule-des"?salience 10 ???begin ???if?Student.score > 80 { ??????println(Student.Name, FormatInt(Student.score, 10),?"及格") ???}else{ ??????println(Student.Name, FormatInt(Student.score, 10),?"不及格") ???} end ` ???ruleAdd = ` rule?"ruleTeach "?"rule-des"?salience 10 ???begin ???if?Student.score < 70 { ??????println(Student.Name, FormatInt(Student.score, 10),?"需要補課") ???} end ` ) func?main(){ ???student := &Student{ ??????Name:?"Calo", ??????score: 100, ???} ???dataContext := context.NewDataContext() ???dataContext.Add("FormatInt", strconv.FormatInt) ???dataContext.Add("println",fmt.Println) ???dataContext.Add("Student",student) ???ruleBuilder := builder.NewRuleBuilder(dataContext) ???err1 := ruleBuilder.BuildRuleFromString(ruleInit) ???if?err1 != nil { ??????panic(err1) ???} ???eng := engine.NewGengine() ???go?func() { ??????for??{ ?????????student.score = rand.Int63n(50) + 50 ?????????err2 := eng.Execute(ruleBuilder,true) ?????????if?err2 != nil { ????????????panic(err2) ?????????} ?????????time.Sleep(1 * time.Second) ??????} ???}() ???go?func() { ??????time.Sleep(3 * time.Second) ??????err2 := ruleBuilder.BuildRuleWithIncremental(ruleUpdate) ??????if?err2 != nil { ?????????panic(err2) ??????} ??????time.Sleep(3 * time.Second) ??????err3 := ruleBuilder.BuildRuleWithIncremental(ruleAdd) ??????if?err3 != nil { ?????????panic(err3) ??????} ???}() ???time.Sleep(20 * time.Second) } |
五:總結:
Gengine將規則文件的配置與程序代碼的編寫進行了一定程度的分離。規則文件采用類編程語言的方式進行編寫,支持簡單的數學運算、邏輯運算、if/else操作、結構體/函數注入等功能,同時能支持規則優先級設置和多種執行模式選擇。規則引擎可以較便捷的通過規則文件的配置來反映實際業務場景中所需要的規則指標,并且能較靈活的適應業務規則的變化。
Gengine是由golang語言開發的,為了實現跨語言協同開發,通常可以將規則引擎封裝為一個獨立運行的規則引擎模塊,通過zmq、mqtt等方式進行數據的接入,根據配置的規則進行業務計算,然后將計算結果對外發布。
Gengine規則引擎也可以搭配rpc、restful等接口,將其封裝為一個獨立的規則服務或計算服務,通過被其它服務調用的方式對外提供計算能力。
在實際的業務場景中通常采用微服務架構,各微服務之間通過rpc、restful等接口進行交互。由于Gengine規則文件支持函數注入,因此甚至可以將已編寫好的接口調用進行事先羅列,在規則引擎中根據規則計算結果進行不同的業務調用。
Gengine的規則文件熱更新功能也為生產環境中不停機更新業務規則提供了可能。
Gengine作為B站開源的號稱“第三代規則引擎”,還有很多其它的一些特性功能等待去研究發現,并將其融入到業務應用中去。
?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Gengine规则引擎的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache ab测试工具使用方法(无参
- 下一篇: 基于 Go 的内置 Parser 打造轻