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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章