背景
一提到规则引擎这四个字,大家肯定多多少少在工作中或者各种文章里面都有过听说,但是很多同学往往被引擎这两个字吓到了,以为这是什么黑科技。时值最近在调研规则引擎,在这里给大家介绍一下什么是规则引擎。
为什么需要规则引擎
规则引擎带来的好处是比较多的,这里我们从不同的角度去剖析一下。
从开发人员视角来看
在没有规则引擎的时代,有些逻辑比较复杂的业务,只有不断的增添if-else去满足我们这个复杂的业务场景,对于开发者来说还好,对于后面接手的同学一看到处都是if-else,体验过的同学就会知道,当然if-else可以通过一些模式去优化,比如使用策略模式,或者使用一些注解进行扩展点优化,这样的确可以解决一部分代码不清晰的问题,但是依然无法解决开发缓慢,需要上线等问题。 举个例子,在风控系统中,因为风控的逻辑在不断的发生一个改变,如果我们在代码中去写死,那么发生一个改变就改一下代码,上一下线,这明显是我们不能接受的。所以我们需要规则引擎去改变这个现状,通过高效可靠的方式去做这些业务规则的改变。
从业务人员视角来看
以前的开发模式是业务人员提出业务规则叫开发人员做出相对应的业务开发,到底这个最后开发出来的业务规则是否和业务人员所提出来的是否一致,需要通过大量的测试去进行验证。而我们的开发人员理解业务很容易和业务人员的提出的业务有偏差,就会导致开发成本上升。有了规则引擎之后,我们就可以有下面几点提升:
- 业务人员独立配置业务规则,开发人员无需理解,让业务人员的规则和真正的实际情况一致。
- 增加业务的透明程度,业务人员配置了之后其他业务人员也能够知道,以前只能通过代码扣扣相传。
- 规则高效改动和上线,一般业务人员提出需求之后都是希望能尽快上线,但是之前都需要有代码开发,项目上线等环节,现在业务人员配置好了之后即配即用。
- 减少业务人员和开发人员的矛盾,开发人员通常会因为一些时间因素或者一些理解不到位导致业务人员的规则实现有偏差,最后业务同学会对开发同学产生一些小小的矛盾,这下完全业务配置解除开了之后,只要不断的升级规则引擎,业务规则就不会再对开发人员有依赖。
什么是规则引擎
说了这么多好处可能很多同学都会疑问,规则引擎到底长啥样呢? 一般来说分为下面三类:
- 低配版:没有配置界面,靠业务人员编写引擎规则DSL,一般存储在数据库或者文件中,这种没有彻底解放业务人员和开发人员的耦合,但是加快了业务代码的上线速度,以及很容易就能进行规则变更。
- 进阶版:这个一般是某种特定的系统,我们针对这种系统设置一些有针对性的页面,比如下面是某风控系统的截图,风控系统的规则引擎是相对来说比较简单的,只需要判断某些参数是否符合某些条件即可,然后返回固定的值即可。
- 完全版:在进阶版中规则引擎只是其中的一个部件,一般这种都很难复用于其他场景。但是一个完全版的规则引擎,追求的超高的通用性,下面是从一个商业的规则引擎中截图:
可以看见提供了多种规则引擎的表达:比如决策集,决策表,决策树等等,适用于我们很多需要使用规则引擎的地方,下面暂时了一下决策树的配置,这个就和我们上面风控的配置有点类似,只不过通用性更强。
讲到这里基本上规则引擎是什么大家基本上心里面有个大概了,下面我们来讲下有哪些开源的规则引擎。
有哪些规则引擎
在社区中开源的规则引擎是比较多的,说明不同的业务团队,公司都对这个是比较看中的,但是整体上大的分类分为下面几类:
- 通过界面配置的成熟规则引擎:这种规则引擎相对来说就比较重,但是因为功能全,也有部分业务会选择这个,一般出名的有:drools,urule。
- 基于jvm脚本语言:这种其实不是一个成熟的规则引擎,他应该算是规则引擎中的核心技术,有很多公司比如美团,他会觉得drools这种太重了,然后会基于一些jvm的脚本语言,去自己开发一个轻量级的规则引擎,这里比较出名的有,groovy,aviator,qlexpress。
- 基于java代码的规则引擎:上面是基于jvm脚本语言去做的,会有一些语法学习的成本,所以就有基于java代码去做的规则引擎,比如通过一些注解实现抽象的方式去做到规则的扩展,比较出名的有: easyRules。
成熟的规则引擎
作为完全版的成熟的规则引擎,往往可以当作sass产品进行售卖,urule再开源部分的同时,也再卖着自己的高级功能,drools是一个纯开源的产品,如果想体验这种规则引擎可以直接去urule.bstek.com/可以体验他的产品,不需…
作为完全版到底是怎么满足各种奇奇怪怪的规则场景呢?在这些规则引擎里面都会分为好几种规则设计器来满足你想要的规则场景:
- 规则集:一组普通规则和循环规则构成的规则集合,是使用频率最高的一种业务规则实现方式。一般分为向导式:通过图形界面构成的;还有脚本式:通过自定义的DSL语言,类似我们下面会讲的jvm脚本规则引擎一样。
这个是我们的向导式规则集,比如我们要写一些if/else/and/or 以及 while循环逻辑的时候我们的规则集是一个非常好的选择。如果要用dsl去写他,需要遵循一些规则语法,下面是drools的dsl:
整体语法来说和我们java差别还是挺大的,有一定的学习成本。
- 决策表:如果我们的业务规则是表格的形式,我们可以使用决策表来进行规则运算,通常我们的产品或者运营人员会给你一个excel表格去执行这些规则,如图:
如果我们想用规则集来实现,也是可以的,但是整体比较复杂,需要大量的写if/else,所以直接使用我们的决策表,就能完成我们的需求:
- 评分卡:如果需要对实体进行综合评分,则可以使用评分卡来进行实现。比如评价一个胆固醇的风险程度:
- 决策树:决策树和其他的都有点不同,比如我们想看到用户风险登记很高的决策规则,如果通过规则集去看我们需要查找所有的规则集,但是决策树不一样,规则都在底部,结果都在顶部,决策树表达的业务更为形象,我们可以根据自己的业务去进行选择合适的规则设计器。下图是一个普通的决策树:
- 规则流:规则流又称决策流,它整个的结构类似于工作流,用来对已有的决策集、决策表、交叉决策表、决策树、评分卡、复杂评分卡或其它决策流的执行顺序进行编排,以清晰直观的实现一个大的复杂的业务规则。编排过程中即可以常见串行执行,也可以并行执行、或者是根据条件选择分支执行。
我们通过这些不同的规则设计器,可以设计出我们不同的规则场景,那么我们应该怎么去调用这些规则呢,一般来说提供了下面三种方式:
- 生成jar包:配置好了之后会生成jar包,然后我们引入到我们项目中,项目调用这个jar包即可。
- 热更新模式:由规则引擎帮助你对这个jar包文件进行热更新,动态的加载到我们的jvm内存中,这种方式不需要重启机器。
- 服务模式:规则引擎自己提供机器,然后通过远程调用的方式,进行规则的计算。
可以根据自己的场景选择合适的调用模式。
Rete算法
不论是drools还是urule,他们都选择了rete算法用作规则匹配。Rete 是一种进行大量模式集合和大量对象集合间比较的高效方法,通过网络筛选的方法找出所有匹配各个模式的对象和规则。其基本原理是通过空间换时间,达到了规则匹配的加速。有兴趣的同学可以下来自行搜索。
jvm脚本语言的规则引擎
drools在互联网公司进行规则引擎调研的时候都会进入备选项,但是往往最后都会以太重,学习成本高而最终落选。往往这种轻量级的脚本语言受互联网公司的青睐。一般来说有下面三种脚本语言比较多的被大家用来做规则引擎:
- Groovy:Groovy是Apache基金会维护的一个脚本语言,它是基于JVM的语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy也可以使用其他非Java语言编写的库。开源的风控引擎radar就是使用的Groovy去实现的。
- aviator:aviator又叫AviatorScript,是一门高性能、轻量级寄宿于 JVM 之上的脚本语言。又叫做表达式语言,提供的语法有限制,和js一样函数是一等公民,支持闭包和函数式编程。最主要它是google开源出来的一个项目,对于他的品质还是非常有保证的。在美团内部基本大部分使用规则引擎的场景比如风控,数据规则等等都选择了aviator这个轻量级的语言作为规则引擎。
- qlexpress:qlexpress是阿里开发的一个脚本语言,在阿里内部以及部分java系的公司都有使用,但是这个我不是太推荐,因为现在这个的社区活跃程度整体的确比较低,上一次更新是一年多前了。
那么这三个jvm脚本语言我们怎么做选择呢?我个人来看的话还是比较推荐aviator,aviator和其他的两个语言不同,他只提供了有限的语法功能,不像groovy是一整套完整的语言,比如可以做一些危险的操作,如果输入了System.exit(0)
可以直接退出我们的进程,但是在aviator是不会提供这种能力的,aviator最开始的时候连if/else,循环都不支持,在最新的5.0版本才支持这些功能,所以他提供的整体功能算是一个安全的沙箱。
Aviator 的基本过程是将表达式直接翻译成对应的 java 字节码执行,整个过程最多扫两趟(开启执行优先模式,如果是编译优先模式下就一趟),这样就保证了它的性能超越绝大部分解释性的表达式引擎,测试也证明如此;其次,除了依赖 commons-beanutils 这个库之外(用于做反射)不依赖任何第三方库,因此整体非常轻量级,整个 jar 包大小哪怕发展到现在 5.0 这个大版本,也才 430K。
回到上面的风控规则引擎,如果我们想实现订单金额大于100元并且用户属于vip这个规则在aviator中应该怎么做呢?
public static void main(String[] args) {
//首先构造参数
Map<String, Object> env = new HashMap<String, Object>();
env.put("orderAmount", 101);
env.put("vip", true);
// 执行表达式逻辑
Boolean result = (Boolean) AviatorEvaluator.execute("orderAmount > 100 && vip", env);
System.out.println(result);
}
// 输出true
可以看见首先我们构造用户是否是vip和订单金额这两个属性,接下来只需要定义orderAmount > 100 && vip
这句表达式,就可以得到我们想到的结果。所以只要运营人员或者产品想到不同的规则,我们这边都可以马上进行配置,可以将这一条规则存到数据库里面,然后进行读取,执行。对于有界面的需求话需要和前端进行配合,让前端的一些控件能自动转换成这种表达式语言,就能完成自动化。
aviator虽然是区别于java的语言,但是其上手成本整体比较低,对于aviator语法有兴趣的可以看看5.0的文档: www.yuque.com/boyan-avfmj…
java代码的规则引擎
基于java的代码规则引擎往往是一种框架,我们基于框架限定的一些条件来进行实现。下面来看一个实例:如果我们有多个加编号的流程,比如猿辅导的我们加上编号前缀tutor
,斑马的我们加上编号前缀conan
,搜题的加上编号前缀solar
,我们的普通写法是怎么写的呢?
if(biz == "猿辅导"){
tradeNo = "tutor" + tradeNo;
}else if(biz == "斑马"){
tradeNo = "conan" + tradeNo;
}else if (biz == "搜题"){
tradeNo = "solar" + tradeNo;
}
通过if/else 进行处理,看起来这种写法也没什么大毛病,其实他破坏了开闭原则,比如我们增加或者修改逻辑的时候都需要去动这一段代码,如果不小心改错了影响到其他逻辑这就得不偿失了。那么我们如何通过easyRule完成我们的这个功能呢?
@Rule(priority = 1)
public class FudaoRule {
@Condition
public boolean isFudao(@Fact("biz") String biz) {
return biz == "猿辅导";
}
@Action
public void process(Facts facts) {
String tradeNo = facts.get("tradeNo");
facts.put("tradeNo", "tutor" + tradeNo);
}
}
@Rule(priority = 2)
public class BanmaRule {
@Condition
public boolean isBanma(@Fact("biz") String biz) {
return biz == "斑马";
}
@Action
public void process(Facts facts) {
String tradeNo = facts.get("tradeNo");
facts.put("tradeNo", "conan" + tradeNo);
}
}
我们实现这两个不同的类,@Rule
注解中定义priority代表我们的if/else优先级, @Condition
就是我们的条件判断,如果属于则进入条件判断,@Action
是我们匹配之后的动作。通过这种方式如果后面再增加或者修改相关逻辑我们可以在不同的类里面去进行修改,也满足了我们的开闭原则。
easyRules也支持使用yaml文件来进行规则的定义,类似我们之前的dsl,但是我觉得实现java类注解的方式是它的大特点,很多同学如果只想选择一些java的扩展框架它的设计思想是一个值得参考,值得学习的框架。