B站新一代golang規則引擎的設計與實現

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着對業務的理解深入和不斷的抽象,可以發現很多業務場景的功能都可以抽象成“規則+指標”的模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種模式,可以應用於很多很場景的場景:如風控場景,識別黑產,需要各種規則來進行判別;如流量分發場景,需要基於各種可收集的指標,組成規則,然後進行鍼對化的內容分發;推薦場景,推薦本身一個基於多指標的典型場景模式(或者說,機器學習就是一個收集數據指標,然後進行學習,進行推廣的過程)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有規則,有指標了,當然還需要一個可以執行規則的引擎。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1. 規則引擎的發展歷程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一代規則引擎,僅支持邏輯運算符(&&, ||, !, 外加括號 ) 主要是用來解析邏輯表達式,通過定義特定佔位符,來綁定具體的子操作,然後使用子操作的結果來進行邏輯運算,並得到邏輯表達式的最終結果:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如,邏輯表達式: \"$1 && $2\", $1 和 $2佔位符 接受規定個數的參數,然後分別輸出true或false,邏輯表達式再對佔位符的結果進行完整的邏輯運算,並得到最終的結果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一代規則引擎特點:簡單;因爲簡單,所以執行性能相當好;但擴展能力不強,可以滿足邏輯判別要求,但無法滿足數值判別要求;當添加新的佔位符運算時,需要工程重新發版。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二代規則引擎,基於某些解釋型語言的規則引擎,如java支持的javascript的執行引擎,那麼規則編寫語言就是javascript,規則引擎就是java虛擬機支持的javascript執行引擎本身。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二代規則引擎特點也很明顯,所引入的解釋型語言有多強,規則表現能力有多強;無需重新發版;但執行性能略差;還有一個致命的缺陷是,使用者爲了配置幾個規則,不得不多學習一門語言,加大了規則的配置難度,以致於不便推廣;不易測試;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三代規則引擎,爲了延續規則的表現能力,同時爲了克服規則的配置難度,爲了免於學習一門新語言的代價,第三代規則引擎將實現規則引擎自身的語言作爲規則配置語言,藉助該語言的自身的發展而發展。同時加入了一些有用的規則屬性,如“規則名稱”、“規則優先級”、“規則描述”。第三代規則引擎的代表時java實現的drools。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三代規則引擎的特點是,規則表現力強,可基於用戶指定的規則優先級,來先後執行規則,同時簡化了一些複雜的不必要的語法。第三代規則引擎適合熟悉規則引擎開發語言自身的開發人員使用,但當推廣至其他人員使用時,依舊免不了要讓不熟悉此語言的人重新學習一門語言(規則配置複雜度並沒有完全消除);同時,也不能完全滿足實時高性能服務場景的需求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2. 規則的執行模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過對各種業務場景的提煉分析,一個規則引擎至少要滿足至少三種執行模式,但實際上,規則執行模式至少有五種,我歸納如下圖所示:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0b/0b52161eb77e9c0eacf773143745d568.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖,"},{"type":"text","marks":[{"type":"strong"}],"text":"順序模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"規則優先級高的先執行,規則優先級低的後執行。這也是drools支持的模式。此模式的缺點很明顯:隨着規則鏈越來越長,執行返回的速度越來越慢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f6/f6761a6b1df0c32a0fac4f530a1b8614.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖,"},{"type":"text","marks":[{"type":"strong"}],"text":"併發執行模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多個規則執行時,不考慮規則優先級,規則與規則之間併發執行。規則執行的返回的速度等於所有規則中的執行時間最長的那個規則。性能優異,但無法滿足規則優先級"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e0/e0f92a497186edb24a7697a742f70b95.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖,"},{"type":"text","marks":[{"type":"strong"}],"text":"混合執行模式(mix model)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選擇規則優先級最高的一個規則最先執行,剩下的規則併發執行。規則執行返回耗時 = 最高優先級的那個規則執行時間 + 併發執行中執行時間最長的那個規則耗時;此模式兼顧優先級和性能,適合於有豁免規則的場景。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/ab775b90d617960b6f057804a17868a3.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖,"},{"type":"text","marks":[{"type":"strong"}],"text":"逆混合模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優先級最高的n-1個規則併發執行,執行完畢之後,再執行剩下的一個優先級最低的規則。和混合模式類似,兼顧性能和優先級。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/64/647aeec6c1a995c2762fce32e806f8e3.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖,"},{"type":"text","marks":[{"type":"strong"}],"text":"桶模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"名字源於"},{"type":"text","marks":[{"type":"strong"}],"text":"桶排序"},{"type":"text","text":",規則池基於規則優先級進行分桶,優先級相同的規則置於一個桶中,桶內的規則併發執行,桶間的規則基於規則優先級順序執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3. B站新一代規則引擎的設計與實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"B站大部分業務線,最開始是使用的是第一代規則引擎。其他使用java的業務線使用的規則引擎是drools。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲初期業務少且簡單,併發量不高,所以選擇使用第一代規則引擎或者drools。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但隨着業務發展,業務請求併發的顯著提升,基於第一代引擎的規則迭代週期長、開發新規則的耗時多。因此有必要開發出一套能滿足業務快速迭代、且健壯的規則引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.1 我們預期的規則引擎的樣子"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1."},{"type":"text","marks":[{"type":"strong"}],"text":"支持規則優先級"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2."},{"type":"text","marks":[{"type":"strong"}],"text":"使用足夠簡單"},{"type":"text","text":"。這其實對規則與具體的代碼的邊界劃分提出了要求。同時,我們我們的目標不僅是讓程序員覺得使用簡單,還要讓無代碼開發經驗的產品、運營同學也能夠覺得使用簡單,讓他們幾乎不需要學習,便能自主配置規則,以此來完成不同的業務需求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3."},{"type":"text","marks":[{"type":"strong"}],"text":"足夠的靈活性"},{"type":"text","text":"。支持完整的邏輯運算,支持完整的四則運算,支持if..else選擇結構,支持用戶自定義API..."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4."},{"type":"text","marks":[{"type":"strong"}],"text":"可選擇的規則執行模式"},{"type":"text","text":"。不同的場景需要不同的規則執行模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5."},{"type":"text","marks":[{"type":"strong"}],"text":"高性能"},{"type":"text","text":"。這當然是最重要的,如果不能滿足高性能,高併發的需求,最終還是會被扔到歷史的垃圾堆中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"6."},{"type":"text","marks":[{"type":"strong"}],"text":"和golang的無縫對接"},{"type":"text","text":"。B站是以golang爲主要開發語言,因此,開發的規則引擎要能和golang無縫對接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"7."},{"type":"text","marks":[{"type":"strong"}],"text":"其他的小確幸:"},{"type":"text","text":"支持註釋...等等"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.2 基於golang與AST(抽象語法樹)的規則引擎實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一代規則引擎解析簡單的邏輯表達式,有的是基於正則實現,有的是基於簡單的AST實現的。如果是簡單的邏輯表達式,正則是足夠用的。如果僅用AST來解析邏輯表達式,顯然又有點大材小用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了不讓抽象語法樹(AST)屈才,也受此啓發,我們最終選用了基於AST來方式來解析和執行具體的規則語法。我們實現規則引擎時所用到的具體技術如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"a."},{"type":"text","marks":[{"type":"strong"}],"text":"基於Antlr4來自定義規則的語法,最終生成語法樹結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"b."},{"type":"text","marks":[{"type":"strong"}],"text":"基於golang的反射技術來實現對用戶自定義API的調用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"c."},{"type":"text","marks":[{"type":"strong"}],"text":"基於golang的併發編程技術實現高性能的規則執行能力"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.1 關鍵核心語法定義"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"grammar gengine;\n\nprimary: ruleEntity+;\n\nruleEntity: RULE ruleName ruleDescription? salience? BEGIN ruleContent END;\nruleName : stringLiteral;\nruleDescription : stringLiteral;\nsalience : SALIENCE integer;\nruleContent : statements;\nstatements: statement+;\n\nstatement : ifStmt | methodCall | functionCall | assignment;\n\nexpression : mathExpression\n | expression comparisonOperator expression\n | expression logicalOperator expression\n | notOperator ? expressionAtom\n | notOperator ? '(' expression ')'\n ;\n\nmathExpression : mathExpression mathMdOperator mathExpression\n | mathExpression mathPmOperator mathExpression\n | expressionAtom\n | '(' mathExpression ')'\n ;\n\nexpressionAtom\n : methodCall\n | functionCall\n | constant\n | mapVar\n | variable\n ;\n\nassignment : (mapVar | variable) (assignOperator | setOperator) mathExpression;\n\nifStmt : 'if' expression '{' statements? '}' elseStmt? ;\n\nelseStmt : 'else' '{' statements? '}';\n\nconstant\n : booleanLiteral\n | integer\n | realLiteral\n | stringLiteral\n | atName\n ;\n\nfunctionArgs\n : (constant | variable | functionCall | methodCall | mapVar) (','(constant | variable | functionCall | methodCall | mapVar))*\n ;\n\ninteger : '-'? INT;\n\nrealLiteral : MINUS? REAL_LITERAL;\n\nstringLiteral: DQUOTA_STRING ;\n\nbooleanLiteral : TRUE | FALSE;\n\nfunctionCall : SIMPLENAME '(' functionArgs? ')';\n\nmethodCall : DOTTEDNAME '(' functionArgs? ')';\n\nvariable : SIMPLENAME | DOTTEDNAME ;\n\nmathPmOperator : PLUS | MINUS ;\n\nmathMdOperator : MUL | DIV ;\n\ncomparisonOperator : GT | LT | GTE | LTE | EQUALS | NOTEQUALS ;\n\nlogicalOperator : AND | OR ;\n\nassignOperator: ASSIGN ;\n\nsetOperator: SET;\n\nnotOperator: NOT;\n\nmapVar: variable LSQARE (integer |stringLiteral | variable ) RSQARE;\n\natName : '@name';"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在語法定義好之後,使用idea的antlr4插件,生成遍歷語法樹的listener和visitor模式的代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"語法樹的可擴展能力決定了規則引擎的可擴展能力"},{"type":"text","text":",當需要爲規則引擎新增功能時,僅需修改語法定義,重新生成代碼即可。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.2 定義語法樹節點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體的代碼請查看github:"},{"type":"link","attrs":{"href":"https://github.com/rencalo770/gengine/tree/master/base","title":null},"content":[{"type":"text","text":"https://github.com/rencalo770/gengine/tree/master/base"}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4b/4b1ea41e443dd5fdb27fb84ab3e6733a.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.3 使用listener模式來遍歷語法樹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用listener遍歷模式,將定義的語法樹節點對應到具體的節點代碼實現上"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"//此golang文件的更多代碼請查看github:\n//https://github.com/rencalo770/gengine/blob/master/iparser/GengineParserListener.go\nimport (\n\t\"gengine/base\"\n\t\"gengine/core/errors\"\n\tparser \"gengine/iantlr/alr\"\n\t\"github.com/antlr/antlr4/runtime/Go/antlr\"\n\t\"github.com/golang-collections/collections/stack\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc NewGengineParserListener(ctx *base.KnowledgeContext) *GengineParserListener {\n\treturn &GengineParserListener{\n\t\tStack: stack.New(),\n\t\tKnowledgeContext: ctx,\n\t\tParseErrors: make([]string, 0),\n\t}\n}\n\ntype GengineParserListener struct {\n //繼承antlr生成的基礎架構代碼\n\tparser.BasegengineListener\n\tParseErrors []string\n\n\tKnowledgeContext *base.KnowledgeContext\n\tStack *stack.Stack\n\truleName string\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.3 其他核心構件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RuleBuilder.go 用於從字符串解析出具體的語法樹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"KnowledgeContext.go 用於存儲解析出來的規則"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DataContext.go 是用戶向規則引擎中添加可用API的接口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Gengine.go 提供各種規則執行模式的接口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.4 規則引擎支持的執行模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"規則引擎當前支持的執行模式有三種"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"a.順序執行模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"b.併發執行模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"c.混合執行模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.5 規則引擎測試"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個超級測試:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼見於: "},{"type":"link","attrs":{"href":"https://github.com/rencalo770/gengine/blob/master/test/Gengine_base_test.go","title":null},"content":[{"type":"text","text":"https://github.com/rencalo770/gengine/blob/master/test/Gengine_base_test.go"}]}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"import (\n\t\"fmt\"\n\t\"gengine/base\"\n\t\"gengine/builder\"\n\t\"gengine/context\"\n\t\"gengine/engine\"\n\t\"github.com/sirupsen/logrus\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype User struct {\n\tName string\n\tAge int64\n\tMale bool\n}\n\nfunc (u *User)GetNum(i int64) int64 {\n\treturn i\n}\n\nfunc (u *User)Print(s string){\n\tfmt.Println(s)\n}\n\nfunc (u *User)Say(){\n\tfmt.Println(\"hello world\")\n}\n\nconst (\n\tbase_rule = `\nrule \"測試\" \"測試描述\" salience 0 \nbegin\n\t\t// 重命名函數 測試; @name represent the rule name \"測試\"\n\t\tSout(@name)\n\t\t// 普通函數 測試\n\t\tHello()\n\t\t//結構提方法 測試\n\t\tUser.Say()\n\t\t// if\n\t\tif !(7 == User.GetNum(7)) || !(7 > 8) {\n\t\t\t//自定義變量 和 加法 測試\n\t\t\tvariable = \"hello\" + (\" world\" + \"zeze\")\n\t\t\t// 加法 與 內建函數 測試 ; @name is just a string \n\t\t\tUser.Name = \"hhh\" + strconv.FormatInt(10, 10) + \"@name\"\n\t\t\t//結構體屬性、方法調用 和 除法 測試\n\t\t\tUser.Age = User.GetNum(8976) / 1000+ 3*(1+1) \n\t\t\t//布爾值設置 測試\n\t\t\tUser.Male = false\n\t\t\t//規則內自定義變量調用 測試\n\t\t\tUser.Print(variable)\n\t\t\t//float測試\t也支持科學計數法\t\t\n\t\t\tf = 9.56\t\t\t\n\t\t\tPrintReal(f)\n\t\t\t//嵌套if-else測試\n\t\t\tif false\t{\n\t\t\t\tSout(\"嵌套if測試\")\n\t\t\t}else{\n\t\t\t\tSout(\"嵌套else測試\")\n\t\t\t}\n\t\t}else{ //else\n\t\t\t//字符串設置 測試\n\t\t\tUser.Name = \"yyyy\"\n\t\t}\n\t\t\n\t\tif true {\n\t\t\tSout(\"if true \")\n\t\t}\n\t\tif true{}else{}\nend`)\n\nfunc Hello() {\n\tfmt.Println(\"hello\")\n}\n\nfunc PrintReal(real float64){\n\tfmt.Println(real)\n}\n\nfunc exe(user *User){\n\t/**\n\t 不要注入除函數和結構體指針以外的其他類型(如變量)\n\t */\n\tdataContext := context.NewDataContext()\n\t//注入結構體指針\n\tdataContext.Add(\"User\", user)\n\t//重命名函數,並注入\n\tdataContext.Add(\"Sout\",fmt.Println)\n\t//直接注入函數\n\tdataContext.Add(\"Hello\",Hello)\n\tdataContext.Add(\"PrintReal\",PrintReal)\n\n\t//初始化規則引擎\n\tknowledgeContext := base.NewKnowledgeContext()\n\truleBuilder := builder.NewRuleBuilder(knowledgeContext, dataContext)\n\n\t//讀取規則\n\tstart1 := time.Now().UnixNano()\n\terr := ruleBuilder.BuildRuleFromString(base_rule)\n\tend1 := time.Now().UnixNano()\n\n\tlogrus.Infof(\"規則個數:%d, 加載規則耗時:%d ns\", len(knowledgeContext.RuleEntities), end1-start1 )\n\n\tif err != nil{\n\t\tlogrus.Errorf(\"err:%s \", err)\n\t}else{\n\t\teng := engine.NewGengine()\n\n\t\tstart := time.Now().UnixNano()\n\t\t// true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule\n\t\terr := eng.Execute(ruleBuilder, true)\n\t\tend := time.Now().UnixNano()\n\t\tif err != nil{\n\t\t\tlogrus.Errorf(\"execute rule error: %v\", err)\n\t\t}\n\t\tlogrus.Infof(\"execute rule cost %d ns\",end-start)\n\t\tlogrus.Infof(\"user.Age=%d,Name=%s,Male=%t\", user.Age, user.Name, user.Male)\n\t}\n}\n\nfunc Test_Base(t *testing.T){\n\tuser := &User{\n\t\tName: \"Calo\",\n\t\tAge: 0,\n\t\tMale: true,\n\t}\n\texe(user)\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"儘管測試看起來形式過於複雜,但規則本身只有4種形式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"a.邏輯運算"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"b.四則運算"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"c.if...else結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"d.函數API調用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"a、b、c三種形式,學過簡單數學的都會用,d形式,僅需簡單識別即可,爲了進一步簡化d,可以固定一個函數接口,基於改變指標名稱的方法取不同的值。如下"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//如果golang支持方法重載,這種用法就美滋滋了:\n//配置規則的人就可以不必關心函數的返回是什麼,他只需要知道,傳入“指標名” + 參數,就能獲得他想要的數據,\n//剩下的一切交給規則引擎來打理\nDataService.GetData(\"指標名\", \"參數1\",\"參數2\"...)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到這裏,任何人,只要學會這4種形式就可以開始配置規則了。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.gengine規則引擎在B站的使用情況"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"B站使用情況"},{"type":"text","text":":當前已經接入了數個場景,並在逐步擴展至更多的業務場景(因爲業務脫敏需要,所以在此詳述)。 B站在此規則引擎上構建了規則管理系統,用於在界面上配置規則,並向各個業務場景實時動態下發規則,無需重啓服務。爲了使規則配置更加簡單,我們開發了一套可複用於所有場景的指標管理系統(這個由我的另一個同事研發實現)。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"關於性能"},{"type":"text","text":":真實的線上grpc服務,單場景線上10個規則,10個4核8G的docker容器,壓測15分鐘,平均2萬QPS, 平均響應耗時在2到4ms,也有極個別請求的耗時在700ms左右,但不超過750ms;配合實體機使用效果會更好。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"5.gengine規則引擎對機器學習的支持"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果大家看過周志華的《機器學習》這本書,大家一定知道,“規則學習”(我們配置的規則)也是一種機器學習,“規則學習”也是符合一般機器學習的理論和方法的。如具體場景的“規則學習”針對具體的場景識別或處理,也有準確率、精度、召回等一般機器學習在概念上的對應。規則引擎對於這種“硬”“規則學習”顯然是天然支持的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼,規則引擎如何支持更一般的機器學習模型調用呢?通常訓練出來的模型最終會暴露出對外服務的接口API,這是函數來支持的,也是在3.2.5小節說的形式d(定義函數)來支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,通常會有很多模型的待識別數據具有很多feature,feature其實就是規則中的指標。因此,基於函數接口取指標完全是OK的。如果從規則外部基於網絡連接來取指標,而且是順序取(規則上不可能支持複雜的併發支持,因爲這樣會破壞簡單性,所以一般也是順序取),那麼這樣帶來的結果就是嚴重拖慢規則執行速度。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於此考量,在未來不久的時間,我們會在語法樹上定義一個簡單的語法塊,用以支持規則內的併發調用,形式大致如下:"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nrule \"ruleName\" \"discri\" salience 10\nbegine\n//用此關鍵字來調用底層複雜的併發能力\nconc{\n\tmetric1 = DataService(\"指標1\",\"參數1\",\"參數2\"...)\n metric2 = DataService(\"指標2\",\"參數1\",\"參數2\"...)\n //\n metricn = DataService(\"指標n\",\"參數1\",\"參數2\"...)\n}\nres = MachineService.predict(metric1, metric2, ..., metricn)\nend"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上,基於語法塊 “conc{...}”,就實現了“指標1”到“指標n”的併發取參數,最終n個指標的調用耗時不是累加,而是等於這n個指標調用中的最長耗時的那個指標。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"6規則引擎代碼實現的github地址"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/rencalo770/gengine","title":null},"content":[{"type":"text","text":"https://github.com/rencalo770/gengine"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"7.聯繫我"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你有任何疑問或好的建議,可以隨時給我發送郵件,或在github上提問:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我的公司郵箱:[email protected](此郵箱可能會被禁止接受外部郵件)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我的校友郵箱:[email protected]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章