作者 | 贴吧 UEG 技术组
导读
本文首先介绍了规定引擎的应用场景,引出贴吧规定引擎。从组件、变量、规定、处理四个模块介绍了规定引擎的组成部分,同时对最终规定文件的编译过程做了具体介绍。为了做到低代码,在规定配置上做到平台化,非研发同学即可实现。减少新的变量也只须要在变量平台进行简略操作,无需额定的代码提交。另外框架层面反对并行和异步的封装,在服务调用上也尽量做到缩小代码,进步研发同学的效率。最初文章对贴吧规定引擎做了总结,也提供了一些常见的应用场景和思路。
全文 6951 字,预计浏览工夫 18 分钟。
01 背景
百度贴吧是一个领有 10 多年历史的 UGC 产品,在业务迭代中难免会有很多业务逻辑的代码,其中一部分业务逻辑用 if-else 等硬编码的模式开发,一部分引入了配置文件,通过配置文件的规定去执行不同的业务逻辑。在某些经营流动或权利规定中,须要频繁减少或者更改一些规定,这部分规定常常变动的局部就须要规定引擎来对立治理。
规定引擎是一种专一于业务规定的服务,它能够将业务规定从代码中剥离进去,应用事后定义好的语义标准来实现这些剥离进去的业务规定。规定引擎通过承受输出的数据,进行业务规定的评估,并做出业务决策。
因为规定引擎将简单的业务逻辑从业务代码中剥离进去,所以能够显著升高业务逻辑实现难度;同时,剥离的业务规定应用规定引擎实现,这样能够使多变的业务规定变的可保护,配合规定引擎提供的良好的业务规定设计器,不必编码就能够疾速实现简单的业务规定,同样,即便是齐全不懂编程的经营或者产品人员,也能够应用图形化的界面来自定义规定,实现代码一样的成果。
上面对一些须要应用规定引擎的场景进行举例:
1、单规定迭代
用户标签 -> 蕴含 A 关键词 -> 权利 A
用户标签 -> 蕴含 A 关键词 -> 权利 A
-> 蕴含 B 关键词 -> 权利 B
用户标签 -> 身份豁免策略 / 机器账号 -> 蕴含 A 关键词 -> 权利 A
-> 蕴含 B 关键词 -> 权利 B
用户标签 ->A 模型后果大于 1 -> 豁 C 类用户 -> 蕴含 A 关键词 -> 权利 C
可见随着业务的倒退,须要一直的调整权利规定,这部分如果硬编码写死在代码中,须要频繁上线,减少了工作量,并且随着业务逻辑的增多,前期保护老本增高。
2、继续接入新的能力
除了目前的字符串比拟能力,个别的规定引擎还会接入各种各样的模型能力,个别通过 RPC 的模式申请不同的服务,随着接入的服务越来越多,能够组合的规定也是成倍的增长;
比方新接入图片模型辨认后,所有图片辨认的后果会过其余相干的模型,相干的模型调用逻辑就减少了一倍;
又如接入了某些模型,要依据模型的分数做相应的解决调整,须要频繁的改变分值对应的处理伎俩,同时为了应答突发的场景,也须要频繁的更改规定。
这些操作如果没有一个自动化的规定引擎,就须要把大量的规定逻辑写在代码里,通过长时间的迭代,规定变得十分臃肿,无论对后续的开发还是定位问题的效率都会带来问题。
02 贴吧规定引擎组成部分
贴吧规定引擎要做到规定灵便可配,无需研发染指,就须要尽可能的把蕴含判断逻辑的局部全副下放到平台,通过平台的勾选对规定进行实现。
上图为规定引擎整体的模块划分,次要分为四局部:
- 组件服务:组件服务是对第三方服务的封装,比方调用图片模型服务、调用帖子属性等内容服务,个别是 RPC 调用;组件须要 RD 开发代码,然而贴吧规定引擎的组件调用不掺杂业 务逻辑,仅仅是定义一个函数 function,通过函数的入参调用第三方服务返回后果;
- 变量平台:变量又称算子,是配置规定的参数;变量分为业务调用时传的入参、应用组件返回的后果等。贴吧规定引擎通过专用平台治理变量,RD 和 PM 均能够在平台上配置变量;
- 规定引擎:规定引擎平台波及到了具体每一条规定,通过图形化的界面生成规定,该平台不须要 RD 染指,通过平台化的操作生成具体的规定。
- 处理办法:该处理为 RD 定制化开发,针对帖子、用户或其余场景的召回处理解决。个别定义一个 rpc 申请回调相干业务,处理办法因为是场景定制化的,所以这部分须要研发染指开发,然而处理办法更新的频率非常低,个别都是复用已有的能力。
2.1 组件服务
规定引擎所有配置的数据不可能都是上游参数传递,很多是通过调用第三方服务获取;比方通过帖子 id 获取的帖子详情数据,通过用户 uid 获取用户的扩大属性,这里都须要调用第三方服务;
组件的开发非常简单,只须要申明一个函数,并实现其静态方法。为了后续的性能思考,函数申明时能够指定 sync(串行)、async(异步)、parallel(并行)三种执行形式,贴吧规定引擎会在调度的时候依照类型,应用更高效的形式执行对应的办法。
图中给出了一个 demo 组件,能够看出组件是不关注业务的,能够自定义入参和返回值,具体调用函数的入口及参数也不须要额定关注,更合乎 lib 库或者 util 办法的实现形式,这种组件的益处是开发简略,解耦业务逻辑,减少组件的复用性,同时也升高了研发同学的工作量。
另外对于 mode 的工作模式,分为以下三类,具体的实现都是框架实现,组件的开发方不须要关注:
- sync:同步调用,应用的时候串行执行,函数间是阻塞的;
- async:异步调用,定义 function 的时候分为 before、after 两组办法。Before 阶段为发动 rpc 申请,期待第三方服务回调后执行 after 办法,能够应答好耗时的服务接入。
- parallel:并行模式,属于同一层级的 parallel 函数并行执行,相似于多线程或者 golang 的 goroutine 模式,目前贴吧的规定引擎采纳 php 开发,不具备多线程的相干能力,所以这里并行是在 before 组装 rpc 参数,通过 curl\_multi 对立发动并行申请,在 after 函数取到后果。
以上能力在规定引擎框架上曾经封装,组件的研发 RD 只须要关注 PRC 的实现即可,依据函数的定义框架实现并行或者异步的调用。
2.2 变量平台
变量或者算子就是规定引擎中做规定判断应用的参数,比方用户名、帖子 id、用户等级、帖子内容、模型辨认的后果等,这部分的内容越多,规定引擎能够创立规定的『素材』越多;
变量的起源分为三个局部:
(1)平台预约义的变量:比方一些常量数字或者特定字符串,这部分内容比拟固定,变动较少。
(2)业务入参:业务在申请规定引擎的时候能够把尽可能多的参数变量传递过去,除了帖子、用户、吧相干的数据,还能够把用户 ip、ua 等各种数据一并传递,这些数据在变量的平台化界面上能够做简略的筛选或者摘取,生成新的变量。
如图举例:input 是一个实现的业务申请的变量,取 input 中的 title 生成 testTitle 变量,这样就能够独自应用 testTitle 做一些规定上的判断。
(3)组件调用:在组件的局部曾经定义了具体的办法,该办法相似于 lib 库或者 util,具体的申请的入口在变量平台实现,入参就是其余变量。
如图举例:testRPC 是定义的组件,其中入参是 testTitle 变量,返回的大后果作为一个 testRPC 变量。
前面能够对 testRPC 变量做具体的拆分,比方 testRPC 中的 data.score 作为一个独自的变量,用 score 这个变量做前面的规定定义。
整体来说,input 是上游传过来的根底变量。对入参变量的整顿和过滤能够生成额定的根底变量;应用根底参数(比方帖子 id),通过 rpc 调用,能够生成扩大的后果;对后果的提取能够生成额定的第二级变量;进一步对二级变量持续调用服务,能够生成更多的变量:比方通过图片模型后果过一些文本模型,然而随着层级的变深,整体服务出现多级依赖关系,这也减少了整体的零碎耗时。
对于入参或者 RPC 申请后果的解决,全副能够在变量平台上进行操作,变量平台定义了规定引擎能够应用的变量及具体的实现形式。因为变量平台反对编辑代码,所以个别的变量都能够间接在变量平台编辑实现,而对绝对简单的模型调用,则能够封装通用的 util 办法,之后在变量平台间接应用这些办法。
2.3 规定引擎平台
组件是一个个简略的 util 静态方法,通过入参及调用组件生成扩大的变量;自此进行规定判断的『素材』筹备好了,接下来须要应用这些变量配置规定,而外围的规定引擎平台就是应用这些变量,生成规定。
规定引擎目前反对的运算规定:
- 规定运算符:目前反对变量值与常量的比拟,蕴含根本的 >,<,>=,<=,==,!= 多种比拟形式;另外不仅能够间接应用变量,对于数组类型的变量,还能够间接应用了变量的计数 count,对于 string 类型,能够应用变量的长度 len 做参数间接进行判断。
-
词表比拟:判断某个词是否在词表中是一个比拟罕用的规定;规定引擎平台反对本地词表与近程词表;近程词表为了解决词表量级太大的问题。
字符与词表的比拟包含准确匹配,蕴含、不蕴含、前缀匹配及后缀匹配几种形式,根本笼罩了常见的应用形式。
-
粒度管制判断:为了判断某个调整在一段时间内的呈现次数,平台反对配置变量的呈现次数统计。
对于某些应用频次较少的运算规定,平台不在性能上进行对立反对,然而能够通过批改变量来反对。比方想判断变量 A 的 sin 值大于 Z,能够在变量平台新配置一个变量 B,它的定位为 sin(A),而后在规定引擎上应用 B 这个变量做判断,就解决了某些非凡的计算形式。
对于判断逻辑,目前反对 if 条件判断,switch 多分支判断,确定召回,确定豁免四种形式,根本囊括了罕用的判断逻辑。
△简略的策略配置 demo
2.4 处理办法
处理办法是针对不同的业务场景召回的个性化处理逻辑,这部分须要 RD 开发代码,做个性化的解决;比方命中召回后返回 true or false 或者命中的规定号或者回调非凡标记。
处理办法增加的频率不会很高,根本固定对帖子、用户或者各个场景有 1 - 2 个处理办法即可,后续的多个规定间接复用处理办法。
03 规定引擎实现原理
规定引擎最终生成是一个蕴含所有规定逻辑的代码块,代码块在规定引擎框架中运行;生成的代码块相似研发开发的代码:代码的逻辑仍旧是定义变量、应用变量做条件判断(规定)、命中召回的处理。
1. 变量
这部分比拟容易了解,就是 2.2 局部;将所有定义的变量取出来,当然因为变量之间是递归依赖的,所以当变量中须要其余变量时,会递归获取内容,直到获取常量或没有依赖为止,最初倒序输入为代码片段。
2. 规定文件生成
每一条规定在存储上都是一个 json 串,存储模式为一个 nodeTree。其中一个 node 节点存储了类型:判断节点、召回节点、豁免节点以及多组 (switch) 判断节点。其中召回节点和豁免节点是程序判断的终止地位,当执行到召回节点时会加载规定引擎对应的处理办法。判断节点是整个规定引擎的外围,蕴含对应的变量与比拟形式。其中比拟形式有数字比拟、字符串比拟及词表比拟。比方内容中是否蕴含关键词“AB”, 则在判断节点上选取内容变量,比拟形式为词表蕴含,词表内容为“AB”。
在规定文件的设计上,采纳 nodeTree 的形式,既能不便后续扩大 node 的属性和类型,又通过父子节点树的形式多层级的示意简单的 if、switch 逻辑,层级能够有限深。
在新的规定上线时,将 nodeTree 文件从数据库中全副导出,生成全副的规定文件。规定文件依赖的变量曾经在变量文件中全副定义好,剩下的工作就是将变量与规定进行组装,生成最终的可执行代码。
另外对于某些非凡的需要,须要对白名单中的 uid 或者类型进行全副策略豁免。对于此类需要能够批改所有的规定,减少前置判断逻辑,然而此操作须要对现有的全副规定及增量规定都批改,且在规定执行中会减少额定的判断逻辑,减少整体规定引擎的执行耗时,所以除了一般的规定外,贴吧规定引擎减少了全局规定区。全局规定区相当于所有规定的前置条件,具体配置的规定为一般的 node 判断节点,当全局的所有规定判断均为 true 时才会顺次执行具体的一般规定,这样对于想全局豁免的需要,只须要简略配置全局规定即可,不须要批改具体的具体规定。
3. 生成可执行规定文件
规定引擎的后期编译工作须要生成能够执行的代码,这部分就是将图形化配置的规定与变量进行组合,优化整体的代码执行逻辑,生成可执行的代码,将文件下发到所有的线上机器。
其中变量文件是能够执行的 php 语法,规定为导出的 json 文件,须要将不同类型的文件进行组合,这里须要将不同文件源转为同一种结构化数据。
对于本来是 php 语法的文件,贴吧规定引擎采纳 ply 和 yacc 进行词法和语法的解析,对 php 语法中 array、函数、赋值、条件判断、运算符等进行提取,转为结构化的数据。
对于规定文件,因为是事后定义好的 json nodeTree,蕴含的格局是无限可枚举的,只有将每种类型与规定映射为结构化的字段,就能够将规定文件转位指标结构化数据。
之后就是可执行文件的生成过程,具体须要以下步骤:
语法树开展:通过递归调用,将函数嵌套开展。比方 res = funA(funB($params))开展为 tmp1 = funB($params);res = funA(tmp1);开展后将高阶函数开展成一般函数,不便后续的优化解决。
接下来就是语句优化局部:
将不同变量重名的局部主动减少 \_n 后缀,防止变量的互相笼罩;遍历整体规定中应用的变量,如果存在变量从未应用过,从整体代码中去除;对于定义了多遍反复的函数调用,整体去重只保留一份;对并行或者异步办法的函数组拆分成真正可执行的静态方法。通过以上步骤,对将要生成的最终规定文件进行了初步的整顿及优化。
在组件服务中提到了异步函数 async;对于某些耗时十分高的模型服务,异步函数的作用是触发调用后完结,期待第三方服务回调。
对于应用异步函数的状况,至多拆分成两步,第一步发动触发,第二步收到模型回调,取到该步骤的后果作为变量,所有依赖该变量的规定只能放到第二步执行。如果有函数依赖第二步的后果,则步骤会持续减少,该函数取某变量的异步后果,发动服务申请,第三步回调收到后果;异步函数开展的作用是将所有无依赖的异步函数申请办法对立放在一起,并行申请,通过回调触发执行第二步的规定逻辑。这样贴吧规定引擎能够很不便的接入高耗时的模型服务。
除了异步函数,还存在一种 parallel 并行调用的形式。因为规定引擎采纳 php 的语言选型,没有其余语言不便的多线程或者协程调用形式,对于无依赖的函数不能反对并行调用,所以在规定引擎的设计上通过 curl\_multil 并行 rpc 调用服务的模式来缩小耗时。
目前比拟耗时的函数个别是申请数据库服务或者第三方服务,这里将数据库及第三方的调用全副封装为 http 协定的模式,在策略文件调用上通过 before 办法整顿入参,通过相似 curl\_multil 的办法并行调用服务,取到后果后执行各自函数的 after 办法,整顿函数对应的变量,这样就将无依赖关系的调用进行了并行处理,整体升高了耗时。并行函数合并就是框架层面做的自动化合并,规定引擎的研发同学只须要简略定义 before 和 after 办法,编译阶段就会主动将所有无依赖的函数 before 办法执行,组装 rpc 申请。如果某些函数在 before 阶段依赖其余服务的后果,那么这批函数将在第二次发动申请,即无任何依赖的函数先发动并行申请,依赖第一批后果的函数再发动第二次并行申请,以此类推,最大限度的应用并行调用的形式。
最终生成的可执行的文件,根本的最小单元为 name、func、param、cond 四组字段组成。如果 cond 判断条件为真,则 name 通过函数和入参数执行对应办法,产出值;该值又是其余单元的条件变量或者函数入参,这样由上到下顺次执行,实现了所有规定的执行。
依然以上述 demo 策略为例:
最终生成了四组单元,根本格局如下:
如图所示,基于上述的规定,只须要四组根本单元,每一组通过函数计算结果,下一组的条件依赖后果的值,如果走到“召回”逻辑,则进行示意规定命中,返回对应的规定号及其处理办法,框架中依据处理办法执行对应的逻辑。
每一个规定都是上述根本单元组成,最终将 nodeTree 中的全副规定生成根本单元,文件下发到所有运行的机器上,至此实现了规定文件的产出与规定上线。
04 总结
贴吧规定引擎搭配图形化的界面,十分不便非技术同学配置业务规定,将冗余的业务逻辑全副托管在规定引擎平台上,无需代码开发,即可上线或者批改规定。
另外框架层面将异步、并行等简单逻辑进行了封装,研发同学调用新的模型只须要依照模版批改简略的参数整顿及返回数据整顿,即可实现并行或者异步的操作,缩小规定引擎的执行耗时。对于变量后果的转换,也能够通过变量治理平台,在平台上简略的批改即可实现一些根本的整顿逻辑,大大减少代码的开发量。
通过规定引擎,能够灵便配置经营流动中的抽奖规定、用户身份权利配置、商品价格等蕴含简单业务逻辑判断的局部,将规定形象进去,解放研发同学的人力,同时规定在平台上能够不便查找和定位,不便后续的保护。
——END——
举荐浏览:
浅谈权限零碎在多利熊业务利用
分布式系统要害门路提早剖析实际
百度工程师教你玩转设计模式(装璜器模式)
百度工程师带你体验引擎中的 nodejs
揭秘百度智能测试在测试定位畛域实际
百度工程师带你探秘 C ++ 内存治理(ptmalloc 篇)