本文从 “为什么需要规则引擎” “规则引擎的定义” “规则引擎在营销活动系统中的落地” “规则引擎平台内部架构” “现有的规则引擎” 来描述。
如果是做业务开发,大家在编程时或多或少都有接触过规则引擎,如果没有可以仔细的来看看这篇文章。 我们的业务代码中往往包含了大量的case,case by case 到处都是条件的判断和选择,当这些if-else、switch庞大到一定程度之后,我们的代码就开始变得难以维护,很多逻辑可能需要看好半天才能盘清楚。除去这种繁重的历史系统,即使每次是新的,我们新写和校对逻辑条件也是一种很痛苦的事情。当写了一遍又一遍的逻辑之后,无非就是在对于业务逻辑进行了一次又一次的决策,我们无法避免大量的逻辑判断,但是可以让这些易变的复杂逻辑从业务中剥离出来。就比如给这团耳机加个绕线器,关键节点咱来把控
image.png
核心问题域有了:大量无法避免的if-else充斥在我们的系统中,对于系统的维护造成了威胁。(系统架构的质量属性:可扩展性、研发质量&效率 严重受损): 1、无法直观表达现有业务逻辑,新人入手困难 2、新增&改动逻辑困难,极难扩展 3、容易出现case遗漏,质量堪忧 4、兜底&通用处理成本高,系统鲁棒性差 5、每次变更逻辑时都需要经历一次完整的研发-测试-发布-回测-灰度 等漫长的研发活动,效率成本堪忧 所以隔离这部分无法避免的业务决策逻辑,让逻辑变得纯粹可独立维护,所有逻辑一目了然动态可配,就是规则引擎要做的事儿。
image.png
简单抽象下业务逻辑判断决策的过程:数据流入规则产生结果。 想剥离这部分数据,那就让我们把整个逻辑判断过程当作黑盒 定义清楚输入输出: 输入:各种条件的具体值(我们if-else决策的值,简单来说就是个k-v的mapping) 输出:决策的结果可能bool、字典值,而这些值又可以作为一组输入来产生新的决策。 核心功能:就是接受输入经过逻辑判断给出输出。 这大体这就是规则引擎的外部概貌了:
image.png
所以一句话来说:规则引擎就是通过接受动态数据流入根据内部的规则得出决策结果的处理器,以抽离业务逻辑保证其独立维护和动态更新。
规则应该长什么样子 到这里还有点没盘清楚的点,我们的规则表达式应该是什么样子,具备什么样的特点: 最简单的我们可以直接用之前的代码表达:
if (AAAA > 10) {
return 1;
} else if (AAAA == 10) {
if (BBB != 0 || CCC >10) {
retuen 2;
} else {
return 3;
}
} else {
return 0;
}
也可以把代码变成真表达式的样子, 顺序匹配就可以了:
rule1: AAA > 10 return 1;
rule2: AAA == 10 && (BBB != 0 || CCC >10) return 2;
rule3: AAA == 10 && BBB == 0 && CCC >= 10 return 3;
rule4: AAA < 10 return 0;
或者说取一下折中,主要看规则的形态和复杂程度是什么样子的,如果是复杂的逻辑各种case并且结果无重合第一种相对合适,如果是层层过滤并且逻辑清晰 结果有重合或优先级,第二种就相对合适了,很多时候取折中就挺舒服,业界也大部分是这样做的。 对于规则的描述核心是:保证功能的前提下,好维护,更清晰。
我们知道规则是做什么的了,也知道规则怎么去做判断长什么样子,但是规则该以何种形态在我们代码中执行呢 目前大致有三种模式:
这个模式相对好理解,在我们的系统中内嵌了一个对于规则语言的解释器,在规则脚本中描述规则逻辑,然后系统传参给解释器并调用对应的脚本,最常见的就是lua/js 这种。
这部分可以直接将所使用的规则脚本编译成我们系统的代码或者一些中间码比如JVM 字节码,运行时动态加载,在运行时状态来看,和我们直接写在代码里几乎没有区别,并且性能要几乎无差。
这一种方式是我们自己定义脚本规范,最经典的就是逆波兰表达式或者自定义Json条件结构,按照约定去编写规则,执行时解析规则然后完成数据匹配及计算得出结果。
一开始的问题域已经把需要规则的引擎的场景抛出来了,那更无脑点来说业界常见的需要规则引擎来解决问题的系统通常有哪些。
风控系统的简单来说就是告诉业务系统这个动作这个人有没有风险,输入有很多:当前用户的设备信息、当前cookie信息、过往操作记录、接入渠道、四项信息 都是有的,然后过程中经过一些列的规则判断,得出风险的结论或者风险的等级,用于业务系统判断该动作是否可以发生或者以什么样的等级进行。 首先风控系统对接的业务会非常多,信贷、营销、下单等,这些业务还需要区分具体产品线,每个业务还会有不同的动作场景,整体来看动作场景的量级已经非常庞大了。对于一个动作场景而言:一系列输入数据还需要经过n层规则匹配和算法过滤出一些中间态因子数据,这些因子再作为规则输入产生结果。很显然整个风控判断规则的数量是非常非常庞大的,这时候规则引擎的存在就非常有必要了,风控系统对于规则场景的诉求也几乎是最强的。
这个跟风控场景相对类似,如果需要对于不同的人给予不同的内容,就必定会有规则存在,这个规则的数量也是十分庞大的(大致跟需要分发的内容数成整理)
在信贷、理财、支付场景会存在一个资金流转的问题,一笔资金并不是像我们所想象的,是一个点到另一个点这样的简单,往往中间会因为合规、收益等n多问题发生资金的流转决策,每一笔交易的过程可能对于业务上:出资账户、中间户是不同的,技术上:经过的系统也是不同的,这就需要我们根据不同的场景来决定这笔钱该怎么走,这就需要各种各样的规则来完成这些流转的控制。
现在都是数据为营的时代,如果想把这些数据高效的利用起来,肯定就要对这些数据进行清洗、打标、加工,才能产生对应的价值。而清洗、打标的过程实际上就是数据过滤规则产生结果的过程,
如最开始提到的,当我们的系统面临那些易变复杂逻辑的时候,当靠硬编系统代码开始吃力时,就需要引入规则引擎来解决问题了。 一点资料: https://zhuanlan.zhihu.com/p/47472836(考拉) https://zhuanlan.zhihu.com/p/140916822(美团) https://zhuanlan.zhihu.com/p/364546754(B站)
上面也提到了规则引擎对于各类业务系统实际上是比较常见的,那么对于活动系统为什么需要呢。从活动出发,先来看下具体的需求特点是否跟规则引擎的要解决的问题域相同: 1、需求量大。 2、倒排需求特别多,大都比较着急,标准迭代模式往往不适用。 最核心的: 3、活动逻辑变动大,每次都是全新的活动逻辑,但每次确又十分相似,具象点:玩法儿就那么多,但是每次针对的人群、奖励规则、玩法儿之间的串联规则都不尽相同。 针对这些特点,引入规则引擎 第一能解决人群规则、奖励规则等易变的问题,在这之上也就同时满足了需求量大、紧急等问题。
在说具体使用场景前先来看下整个营销活动系统的架构,按照交互分层来看一个营销系统大致是这样的:
image.png
表现层主要是完成界面的展示; 触达层主要完成玩法儿与用户的交互; 规则层包括:各种玩法儿内部的规则&玩法儿之间的串联 权益层包括:权益类:现金红包、代币、各种券等;触达类:push、短信、私信等若干通知
很显然规则引擎主要用于规则层的处理: 1、抽奖,不同的人&不同的场景对应不同的奖池(不同的中奖概率、不同的奖品集合) 2、任务,任务领取规则、任务完成指标动态可配(不同的人不同的任务,指标条件可动态配置&组合) 3、通用激励模型,不同的用户特征 对应不同的激励程度(不同的人在不同的场景下,对于奖励的感知程度都是不同的) 4、通用触达模型,差异化文案内容 分别具备不同的情感特征
这个单拎出来说,玩法儿的串联通常是由 事件总线 + 用户参与上下文信息组成的。事件总线就是直接表达事件与事件之间的关联关系,用户参与上下文信息通常是用户参与过程的状态(参与了哪几个玩法儿,已获得多少奖励,是否达成预期目标)。事件匹配后,根据上下文信息进行最终决策的过程就是规则执行的过程。其实如果对于这部分规则及事件之间的串联关系进行集中描述,就更上一层作为活动流程引擎存在了。 整个触发过程可以粗暴的解释为:由源事件匹配所有需要扩散的事件,根据上下文信息进行时间过滤及部分动态计算得出要触发的事件及对应的触发值。
拿上面的所有点合并举个例子:
{根据一定规则指派任务} ,用户达成
{根据用户已收激励给予用户抽奖机会(几次)或直接奖励,并根据参与状态判断决定是否发放私信留存}, 用户拿到抽奖机会后进行抽奖
{根据用户特征计算出用户受用的红包金额},发放奖励。 拆开来看:
{若干指标}
{由于是新用户,将面向现金等奖品池进行抽奖,中奖概率高} ${根据用户特征计算出用户受用的红包金额} 可以很清楚的看出来,整个活动玩法主体逻辑是稳定的,那些易变规则都可以抽象出来可配置,并且活动之间的串联规则都是可随时修改并根据实时情况计算的,这些决策的规则的执行者就是规则引擎。
对于营销活动场景来说: 1、使用人员:运营+部分技术人员 2、规则输入:用户标签信息、参与的上下文信息(已收激励数量、第几次参与、是否转化等) 3、规则输出:一些key值:事件key、任务key、奖池key、金额 4、规则执行:标签匹配过程,大多是bool逻辑判断 很显然相对于其他场景来看营销活动的逻辑相对简单,现有几乎所有的规则引擎都能支持,但是考虑到操作人员有运营同学并且规则量大且非常不稳定(一个活动n条),所以足够简单&清晰、易操作 变成了刚需,个人感觉最适合的就是用逆波兰表达式做一个最基础的规则引擎,虽然一定程度上损耗了性能,常规十万级别qps根本问题不大(比起IO消耗根本不算啥,瓶颈通常不是这里)。对于那种峰值流量巨大要求苛刻的营销活动,可以直接干掉规则引擎部分裸写代码实现。
上面说了那么多规则引擎的好处,但是规则引擎适用的场景其实也就这些了,对于一项技术千万不能过度痴迷,需要针对场景来综合评估落地。如果roi不够,盲目的去落地往往会得不偿失,不仅增加了系统的复杂程度,还要处理新技术带来的一系列问题。 之前看过一个架构设计的思路,以风险作为驱动主导架构设计,在综合评估要设计系统的各种质量属性之后,选用现有的一个和完备的一组技术方案 根据面临的风险的严重程度依次解决,往往能设计出更适合现状的架构。对于规则引擎的选择也是这样,如果真的存在上面说的那些风险,如果引入规则引擎是最合适的解决方式,并且带来的风险较小或可以接受那么就可以选用。 总之,系统越简单越好。
image.png
规则引擎从出现到现在已经发展好多好多年了,业界已经有非常多的现有解决方案了,上面提到的三种模式都是支持的,那三种其实也可以近似的认为是第1,2,3代规则引擎。 我们在使用规则引擎时通常有两种构建思路: 1、当作单纯的sdk来用,一个执行函数而已,我们只需要引入核心库或者自己写个库就能用了。 2、构建统一的规则引擎的平台,这种场景往往是需要大规模的管理规则和执行规则,并且具有完备的降级方案。
一个规则对象由条件、优先级、结果构成,其中条件包含:特征、操作符、阈值 由该领域对象之上定义规则引擎的领域服务:规则管理、规则核心的加载模式支持,最核心的执行(包括入参处理、规则匹配、逻辑运算、结果给出)
image.png
规则引擎服务通常是在核心的规则引擎之上,增加了一些执行时门面服务、可视化规则创建、多种规则引擎支持、更加系统的规则管理体、调用上下文、附加数据支持等服务而已,大致长这样子:
image.png
规则引擎一直到现在看起来都是非常好用但是有几个问题始终比较难解: 1、易用性问题 对于复杂规则通常的直接解释型、编译类的实现对于操作人员来说只不过是代码的位置从系统中牵出来了,整体的书写难度并没有降低太多,反而还要感知新的一些语法规则,着实很头痛。 简单的逻辑表达式类型的规则虽然能通过拖拽来搞定,但是实际操作难度仍然比较高,无脑上手是绝对不可能的,得有一定的学习成本。 2、性能问题 自己定义语法树解析,性能比写代码差了一些,结构的解析,程序的执行栈比写代码要高一个数量级。 那种内嵌解释器或直接编译的场景,虽然性能要好一些,但规则引擎执行冷启动和热替换时还是有不少性能代价的。
Java 中的规则引擎还是挺多的,Go语言的目前貌似不多,生态还是硬伤啊
现有的平台: 1、Drools Java 语言编写的开放源码规则引擎,基于Apache协议,基于RETE算法,于2005年被JBoss收购,用来减少硬编码还是不错的。 2、Easy Rules Easy Rules 提供了规则抽象来创建带有条件和操作的规则,以及运行一组规则来评估条件和执行操作的RulesEngine API。相对易于学习的API。并且支持创建复合规则。能够使用表达式语言定义规则 3、Ilog JRules: IBM WebSphere ILOG JRules 是一个业务规则管理平台,它尽可能减少了研发人员的参与。顺道提供了一整套全生命周期管理工具。
常见的表达式引擎(可用来构建规则引擎): 1、能产生独立class文件的编译型:fel、simpleEL、groovy 2、产生独立内存执行的解析型:aviator、JEXL
直接使用内置解释器 1、Java:Lua/JS 需要新学一份脚本语言了,不过lua 还是很值得学习,openrestry、redis 用起来还挺舒服。
哦对,写到最后发现里面出现规则引擎、表达式引擎、决策引擎还有流程引擎,感觉有些凌乱,谈一下区分观点哈,恳请指正: 1、表达式引擎 核心关注点在于易变逻辑的抽离,通过表达式替换硬编码。 2、规则引擎 是表达式引擎更上一层,核心关注点在于匹配,主要解决规则分散难以维护的问题。 3、决策引擎是规则引擎更上一层,核心关注点在于判断,主要解决选择的问题。 4、流程引擎 是表达式引擎或规则引擎之上,核心关注点在于整体的流程刘庄,主要解决流程节点分散管理不直观的问题。