规则引擎其实不是新兴起的东西了,这个概念的提出其实可以追溯到上世纪,规则引擎起源于基于规则的专家系统,专家系统这东西属于人工智能的范畴了,模仿人类的推理方式的推理引擎,是将逻辑规则应用于知识库以推断新信息的系统的一个组成部分。
先来看一下这个场景:
之前做会员场景的时候会遇到这种需求,根据用户购买的商品价格和品类,赠送不同的积分。
商品价格 | 赠送积分 |
---|---|
<100 | 赠送20 |
<500 | 赠送80 |
>500 | 赠送200 |
如果我们写代码实现的话
const addPointsByOrderPrice = (order)=>{
if(order.price > 500 ){
add(order.account_id, 200);
} else if (order.price >100){
add(order.account_id, 80);
} else {
add(order.account_id, 20);
}
}
过了几天,运营同学说,这个效果不太好,我们再改下规则,变成100-200元的赠送20,200-500元赠送90,500-800赠送100.....如此类推一直到1w。
遇到这样的需求也是没办法了,总不能写n个if else。那就会想着,我把这个规则写配置中心或者数据库,不就ok了吗?
然后就有了以下的代码
let rules = await db.select("select * from rules");
const addPointsByOrderPrice = (order)=>{
for(let rule of rules){
if(order.price > rule.price){
add(order.account_id, rule.point);
break;
}
}
}
然后后面产品又脑洞大开了,觉得这个条件不够精细化,还要根据商品的标签属性,用户标签来判断。如果是新用户就乘二,如果商品是xx活动的特卖商品,积分就加多20。
这样下去正常人非得被产品需求搞死了。可以看出这里活动规则和代码已经耦合在一起了,即使用上配置中心,很多时候还是需要开发同学写硬编码去开发。
这时候就需要有个声明式语言,能把这个规则和玩法描述清楚。规则引擎主要完成的就是将业务规则从代码中分离出来。
在规则引擎中,利用规则语言将规则定义为if-then的形式,if中定义了规则的条件,then中定义了规则的结果。规则引擎会基于数据对这些规则进行计算,找出匹配的规则。这样,当规则需要修改时,无需进行代码级的修改,只需要修改对应的规则,可以有效减少代码的开发量和维护量。
目前最著名的开源规则引擎是Drools。
这是Drools里用到的一个核心算法。Rete算法是一种前向规则快速匹配算法,是一个用于产生式系统的高效模式匹配算法,其匹配速度与规则数目无关。解决的是两个问题:
通过这个算法我们可以了解规则引擎的核心思想。
首先解释几个关于规则引擎的概念:
Fact(事实):对象之间和对象属性之间的关系
Rule(规则):由条件和结论构成的推理语句,一般表示为if…Then。一个规则的if部分称为LHS(left-hand-side),then部分称为RHS(right hand side)。
Module(模式):就是指IF语句的条件。这里IF条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。
DSL(领域语言):领域专家只需要业务,而不需要关注技术
假设系统中有N条规则,平均每个规则的条件部分有P个模式,在某个时点有M个事实需要处理。则规则匹配要做的事情就是:对每一个规则r,判断当前的事实o是否满足LHS(r)=True,如果满足,则将规则r的实例r(o),即规则+满足该规则的事实,加到冲突集中等待处理。通常采取如下过程:
从N条规则中取出一条r;
从M个事实中取出P个事实的一个组合c;
用c测试LHS(r),如果LHS(r(c))=True,将RHS(r(c))加入队列中;
如果M个事实还存在其他的组合c,goto 3;
取出下一条规则r,goto 2;
实际的问题可能更复杂,在规则的执行过程中可能会改变RHS的数据,从而使得已经匹配的规则实例失效或者产生新的满足规则的匹配,形成一种“动态”的匹配链。
目前常见的模式匹配算法包括Rete、Treat、Leaps,HAL,Matchbox等。
假设一个场景,物流分货物。货物的属性有重量,种类,收货地,是否加急。
按照规则引擎的三大概念来划分:
StockFact:该对象存放货物的基本信息。
SelectStockRule: 筛选规则
IF:
Weight < 10kg
Category != 易燃物
收货地 != 新疆
THEN
交给xx航班空运
Rete算法划分
Agenda:一旦一个业务对象匹配了一个规则,会形成该规则和该业务对象的一个议程。即StockFact要把该货物放在空运的事件。
Execution-Engine:业务对象匹配上一个规则后,业务对象执行规则结果的执行器。即将StockFact该货物放在空运中事件的执行器
Rete 算法可以被分为两个部分:规则编译和规则执行 。当 Rete 算法进行事实的断言时,包含三个阶段:匹配、选择和执行,称做 match-select-act cycle。本质上是利用空间换换时间,会消耗较多内存。
AlphaNode:例如。Weight < 10kg 这就是一个 alpha node 节点,当一条规则有多条字面条件,这些字面条件被链接到一起。
BetaNode:用来对2个对象进行对比、检查。比如这两个条件的组合
Weight < 10kg
&&
Category != 易燃物
约定BetaNode的2个输入称为左边(Join Node)和右边。左边通常是一个a list of objects,右边(NotNode)通常是 a single object。每个Bate节点都有自己的终端节点等组成。BetaNode 具有记忆功能。左边的输入被称为 Beta Memory,会记住所有到达过的语义。右边的输入成为 Alpha Memory
一个规则引擎最重要的是Patten Matcher。我们经常用的正则表达式就是一种模式匹配。
Patten Matcher流程展示
1.顺序为ABCDE。ABD为Alpha节点,CD为Beta节点。
2.从WM中拿出Fact,首先先匹配A,A如果条件符合,把Fact的引用存到A的Alpha Cache。A没有左引用的Beta节点,退出。
3.然后匹配B,B如果条件符合,把Fact的引用存到B的Alpha Cache。然后B有左引用的Beta节点,就是C。
4.C节点作为Beta节点,有左引用的节点A,然后找下Alpha Cache有没有A节点的引用。如果有,那就存到C节点对应的Beta节点。退出
5.到D节点的匹配,如果符合条件,把Fact的引用存到D的Alpha Cache。同样找到D的左引用Beta节点E,E的左引用为C。如果C的Beta Cache是否有C的引用,如果有证明所有条件符合,E的引用存在BetaCache中
6.该Fact匹配了这些规则,形成了一个议程,放入冲突区并执行结果。
那如果有多个规则被触发呢?
我们定义了多个规则,每个规则都有不同的显着性,当一条消息被断言时,它们将按照显着性(从高到低)的顺序触发。
PS:显着性是一个可以在规则上指定的选项,赋予它优先级并允许开发人员对激活的冲突解决方案进行一些控制。
上github链接noolsjs/nools[1]。首先要说明的是,Nools这个项目已经停止维护一段时间了,甚至谷歌都没有搜出什么有用的中文文档,都是有段历史的的外文文档,但是官方文档齐全。因此这里不建议大规模使用在生产环境,可能会误伤。
虽然不太能用,但是用他来理解规则引擎的概念也是比较好的,毕竟Nools也是使用Rete算法实现的。
var nools = require("nools");
var flow = nools.compile("./Demo2.nools"),
Param = flow.getDefined("Param");
session = flow.getSession(new Param(11))
.on('assert', function (result) {
console.log(result);//回调返回assert的内容,输出 result2
})
.on('modify', function (result) {
console.log(result, result.param1);//回调返回修改的值,输出{param1:21} 21(无参数note)
})
.match().then(function () {
console.log('end');//不管是否有规则满足都执行,输出end
});
可以看到这里有几个状态,assert,modify。
根据官方文档描述的是
assert (fact) - emitted when facts are asserted
retract (fact) - emitted when facts are retracted
modify (fact) - emitted when facts are modified
fire (name, rule) - emitted when an activation is fired.
使用这几个状态,我们可以轻易的在nools文件里控制网络的流向。
假设目前有nools文件如下
define Param { //作为规则的输入消息
param1 : 0,
note :'test for nothing',
constructor : function(p1){
this.param1 = p1;
}
}
rule condition1 { //定义规则condition1
when { p:Param p.param1 <= 10;}
then { assert("result111");}
}
rule condition2 { //定义规则condition2
when { p:Param p.param1 > 10 && p.param1 <= 20;}
then {
//这些事件的触发都可以是多个
assert('result2'); //触发assert事件
//fire(p);
modify(p,function(){ //触发modify事件
p.param1 -= 10;
});
}
}
它定义了一个两个rule,一个是params <= 10, 一个是 params > 10 && params <=20;
这里不贴图了,直接看代码演示,从DSL语法来更近一步了解Rete网络。
现在我们要设计一个抽奖规则可视化。
除了基础服务如抽奖服务,用户服务,还应该要考虑以下的问题:
可视化规则编辑器
规则自动化测试框架
事实浏览器
mock服务
规则管理和版本
用户不敏感状态的缓存
规则执行
如果要做到能用的话,这里仅需要关注AST和规则引擎即可。
第一部分是可视化规则编辑,虽然规则引擎把逻辑和业务代码分离开,但是DSL真的太不好阅读了。目标当然是方便配置,甚至可以不用开发介入就可以完成规则的配置。假设设计为只需要写这么一行简单易懂的话,就可以生成具体的DSL给规则引擎。
If
(老用户 为 真
和
活跃天数 大于 20)
或
新用户
Then
抽奖机会 + 1
这段话就很简单易懂。这里要引入Parse模块,把这段话转化成DSL。
其实就是语法解析,这里考虑使用Pegjs这个库实现。这里不做进一步的介绍了,有兴趣可以参考https://pegjs.org/online。更高阶的做法是做成拖拽的样式更方便运营同学使用。
第二部分是规则引擎,那就可以自由选择nools或者json-rules-engine来实现。(json-rules-engine也提供了对应的开源编辑器,如果图方便可以直接用这个可视化编辑,虽然我个人觉得很难用)。
以上提供了一个规则生成器的思路,往细里分析的话会发现,以上我定义的都是静态规则,实际生产环境上一定会有动态规则的出现,这里就交给大家在实际业务中去思考了。
业界里使用规则引擎的场景并不少,特别是现在中台概念提出一段时间后,很多复杂逻辑都整合到一个大型系统中。规则引擎的引入能减少很多代码复杂度和编码工作。
规则引擎Tob的标杆就要数IBM的iLog,跟中移动的合作公司的朋友了解过,广东移动上新的计费和套餐就是用这一套的,很多保险公司也是在用iLog。像淘宝美团电商类的优惠券规则,也都会用到规则引擎,不过一般都是自研的。银行的金融风控也会有规则引擎,甚至一些APP像抖音,BigoLive这种短视频APP里也会有规则引擎,模型去做识别,然后作为动态规则去加载对应的图形。
虽然网上查规则引擎,基本都是出来Drools,但是很多时候实际选型上根本不会选它,主要因为
所以像美团,淘宝系这些团队在规则引擎的选型上会更倾向于使用动态脚本引擎,像Groovy,aviator,easyRule这些,这些引擎DSL的写法跟java相似,组件生态跟java无缝贴合。
小规模的规则引擎,还是推荐自己写或者用轻量级的框架要来的快,毕竟很多时候都不会接触到上百万条规则的场景。如果是自己写的话可以考虑直接设计决策树,又能兼容可视化又方便遍历。js的组件可以参考一下CacheControl/json-rules-engine[2]也是一个不错的选择。
PEG.js 介绍与基础使用[3]
nools[4]
[转]规则匹配--Rete 算法原理及实现_我的代码管家---houwenbin的专栏-CSDN博客_rete[5]
别再说你不懂规则引擎啦![6]
RETE算法初窥[7]
[1]noolsjs/nools: https://github.com/noolsjs/nools#flow
[2]CacheControl/json-rules-engine: https://github.com/CacheControl/json-rules-engine
[3]PEG.js 介绍与基础使用: https://zhuanlan.zhihu.com/p/49833910
[4]nools: http://noolsjs.com/#facts-retract
[5][转]规则匹配--Rete 算法原理及实现_我的代码管家---houwenbin的专栏-CSDN博客_rete: https://blog.csdn.net/houwenbin1986/article/details/93893684
[6]别再说你不懂规则引擎啦!: https://juejin.cn/post/6942310146882306084
[7]RETE算法初窥: https://blog.abstiger.com/introduce-rete-algorithm/
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8