一提到规则引擎这四个字,大家肯定多多少少在工作中或者各种文章里面都有过听说,但是很多同学往往被引擎这两个字吓到了,以为这是什么黑科技。时值最近在调研规则引擎,在这里给大家介绍一下什么是规则引擎。
规则引擎带来的好处是比较多的,这里我们从不同的角度去剖析一下。
在没有规则引擎的时代,有些逻辑比较复杂的业务,只有不断的增添if-else去满足我们这个复杂的业务场景,对于开发者来说还好,对于后面接手的同学一看到处都是if-else,体验过的同学就会知道,当然if-else可以通过一些模式去优化,比如使用策略模式,或者使用一些注解进行扩展点优化,这样的确可以解决一部分代码不清晰的问题,但是依然无法解决开发缓慢,需要上线等问题。 举个例子,在风控系统中,因为风控的逻辑在不断的发生一个改变,如果我们在代码中去写死,那么发生一个改变就改一下代码,上一下线,这明显是我们不能接受的。所以我们需要规则引擎去改变这个现状,通过高效可靠的方式去做这些业务规则的改变。
以前的开发模式是业务人员提出业务规则叫开发人员做出相对应的业务开发,到底这个最后开发出来的业务规则是否和业务人员所提出来的是否一致,需要通过大量的测试去进行验证。而我们的开发人员理解业务很容易和业务人员的提出的业务有偏差,就会导致开发成本上升。有了规则引擎之后,我们就可以有下面几点提升:
说了这么多好处可能很多同学都会疑问,规则引擎到底长啥样呢? 一般来说分为下面三类:
可以看见提供了多种规则引擎的表达:比如决策集,决策表,决策树等等,适用于我们很多需要使用规则引擎的地方,下面暂时了一下决策树的配置,这个就和我们上面风控的配置有点类似,只不过通用性更强。
讲到这里基本上规则引擎是什么大家基本上心里面有个大概了,下面我们来讲下有哪些开源的规则引擎。
在社区中开源的规则引擎是比较多的,说明不同的业务团队,公司都对这个是比较看中的,但是整体上大的分类分为下面几类:
作为完全版的成熟的规则引擎,往往可以当作sass产品进行售卖,urule再开源部分的同时,也再卖着自己的高级功能,drools是一个纯开源的产品,如果想体验这种规则引擎可以直接去urule.bstek.com/可以体验他的产品,不需…
作为完全版到底是怎么满足各种奇奇怪怪的规则场景呢?在这些规则引擎里面都会分为好几种规则设计器来满足你想要的规则场景:
这个是我们的向导式规则集,比如我们要写一些if/else/and/or 以及 while循环逻辑的时候我们的规则集是一个非常好的选择。如果要用dsl去写他,需要遵循一些规则语法,下面是drools的dsl:
整体语法来说和我们java差别还是挺大的,有一定的学习成本。
如果我们想用规则集来实现,也是可以的,但是整体比较复杂,需要大量的写if/else,所以直接使用我们的决策表,就能完成我们的需求:
我们通过这些不同的规则设计器,可以设计出我们不同的规则场景,那么我们应该怎么去调用这些规则呢,一般来说提供了下面三种方式:
可以根据自己的场景选择合适的调用模式。
不论是drools还是urule,他们都选择了rete算法用作规则匹配。Rete 是一种进行大量模式集合和大量对象集合间比较的高效方法,通过网络筛选的方法找出所有匹配各个模式的对象和规则。其基本原理是通过空间换时间,达到了规则匹配的加速。有兴趣的同学可以下来自行搜索。
drools在互联网公司进行规则引擎调研的时候都会进入备选项,但是往往最后都会以太重,学习成本高而最终落选。往往这种轻量级的脚本语言受互联网公司的青睐。一般来说有下面三种脚本语言比较多的被大家用来做规则引擎:
那么这三个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的代码规则引擎往往是一种框架,我们基于框架限定的一些条件来进行实现。下面来看一个实例:如果我们有多个加编号的流程,比如猿辅导的我们加上编号前缀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的扩展框架它的设计思想是一个值得参考,值得学习的框架。