cover_image

规则引擎系列篇(1)初出茅庐

何信麟 (达伦) 云筑网技术团队
2021年12月10日 01:52
图片

云筑网技术团队

助推建筑行业数字化


1 什么是规则引擎

在现实生活中处处都充满了规则,不同的规则对人起到了不同的指导和约束的作用。而程序就是对现实生活的描述,自然也会有很多的规则,电商系统中满足不同的购物金额即送特定的积分就是一个规则、风控系统中对于满足不同条件的用户请求进行过滤也是一种规则。在没有规则引擎之前,我们进行程序开发时,程序中除了本身的业务代码外,还会有大量的逻辑代码。在逻辑规则相对稳定和不可变时,不会有任何问题,但是在电商、风控等系统中,其中的规则就会往往是多变的,由于业务代码和逻辑代码互相嵌套,错综复杂,当规则发生变化时,就需要经过开发->测试->上线的完成流程才能完成对新规则的线上应用。而规则引擎的即是:将业务代码与逻辑代码分离,两部分可进行独立扩展,降低了程序的维护成本和扩展性成本,当规则变化时,能够实现快速、低成本的更新。

图片

2 为什么使用规则引擎

使用规则引擎有以下好处:

1.  能够更好的将业务变化从系统研发中剥离出来,使得业务人员和研发人员能够更好的专注于自己的模块。

2.  当业务规则变化时,能够避免系统经历完整的需求->开发->测试->上线流程,大大提高了需求响应时间。

3.  业务规则模块和业务逻辑模块,两部分能够独立变化,这是一种更好的设计模式。

4.  规则的设计可以依赖与规则设计器提供的可视化视图,使得非专业技术人员也能参与到规则的编码中。

图片

3 常用规则引擎产品有哪些

图片

4 规则引擎的基础知识

规则引擎从总体上看分为规则文件和规则执行两部分。规则文件部分主要是对规则文件进行设计、编码和管理,而规则执行部分则是接收指定入参,然后运用规则文件进行动态解析,输出目标结果。

4.1 规则文件

规则文件是一种对规则进行定义和描述的文件。如下示例规则文件,用于指定一个规则classRule,判断学生的年龄,如果是6岁,则班级应该设定为一年级,如果是7岁,则班级应该设定为二年级:

rule "classRule1"  when       student: Student(age==6)       then student.setClassName("一年级");  end
rule "classRule2" when student: Student(age==7) then student.setClassName("二年级");  end  

如上所示,其实规则的定义主要是通过规则体进行描述,规则体分为LHS、RHS两大功能模块。

· LHS:全名Left Hand Side,即规则体中的条件部分,就如业务代码程序一样,条件是多重多样的,所以LHS可以包含0-n个条件,如果LHS中包含0个条件,则表示当前的判断条件结果永远为true。

· RHS:全名Right HandSide,即规则体中的执行部分,只有满足LHS的条件后,才会执行RHS中的动作。RHS是规则体中真正做事的那一部分,即要处理和返回结果的那一部分。

· Fact:规则引擎中,Fact是一个很重要的概念。每一个规则的是否执行依赖于LHS中的条件,而这些条件是否成立,则依赖于Fact对象。Fact对象是规则条件中的入参,它可以是一种基本类型,也可以是面向对象语言中的POJO,规则体会对传入Fact对象进行读写,从而可以完成规则引擎和业务代码的数据交互。

以上规则只是一个最简单的规则示例,实际场景中的规则可能远比这个复杂,对于复杂的规则定义我们有可以采用不同的模式来定义。接下来,我们看看几种对规则文件进行变形而形成的模式。

1.  决策表

决策表是规则文件的一种变形,是以excle表格方式对规则进行描述的,它以一种“行列”的条件逻辑模式,非常适合业务场景规则。因为其使用表格的方式,使得定义规则的门槛非常低,应用领域非常广泛。上面的classRule用决策表可以如下描述:

条件

动作

student.age==6

student.className="一年级"

student.age==7

student.className="二年级"

2.  评分卡

对于个人或者企业的评分是一种非常常见的场景,常见到规则文件为其设计了评分卡这种模式。评分卡主要通过二维的形式对评分对象的各种属性设置不同的区间条件,并为不同区间设定不同的分值。以下是一个信用系统的示例:

条件

得分

按时还信用卡

+5

不按时还信用卡

-5

存入金额大于1万&&小于等于5万

+10

存入金额大于5万

+15

3.  决策树

决策树是以一种树形结构的方式对规则进行定义,树形结构在系统的架构设计中很常见,它能够以一种更加直观、形象和生动的方式描述规则,上文中的信用系统的规则通过决策树可以这样表示:

图片

4.  规则集

决策表、评分卡、决策树都是以一种形象的方式对规则进行定义和描述,而规则集其实就是一种最原始,但是又是功能最强大的规则模式。它采用特定的规则定义语言对规则进行描述,比如drools的drl文件、dsl文件和dslr文件,aviator的av文件。如下示例,使用aviator语言对集合进行操作,这在定义规则动作时,是非常常见的:

use java.util.*;
let list = new ArrayList(10);
seq.add(list, 1);seq.add(list, 2);
p("list[0]=#{list[0]}");p("list[1]=#{list[1]}");
let set = new HashSet();seq.add(set, "a");seq.add(set, "a");
p("set type is: " + type(set));p("set is#{set}");

5.  规则流

规则流是一种规则+流程的运用模式,其分为两部分规则和流程,采用流程的方式将多个规则进行组合、串联。

· 规则部分:规则流中的规则往往是多个的(单个规则不需要用到流程),其中的每个规则的定义可以采用决策表、评分卡、决策树和规则集四种规则模式的中任意一种。

· 流程部分:即规则流程,流程引擎非常多,主流的流程引擎包括JBPM、Activiti、Camunda等。它们的核心功能都一样,将多个动作使用顺序或者分支的方式进行串联。

图片

4.2 规则执行

规则文件和规则流只是定义了规则本身和规则的执行顺序,而规则执行部分才是规则引擎能够解藕业务规则和业务代码的关键。总体上说,规则的执行包含:规则文件集合、入参Fact对象、计算引擎和输出。计算引擎接收规则文件集合和入参Fact对象,经过计算后,输出业务需要的结果。

图片

规则的执行分为两种模式客户端模式和服务端模式:

客户端模式:规则引擎提供规则库的管理,当业务方需要使用规则的时候,通过在规则库定义规则和规则引擎提供的客户端SDK就能够完成对规则的获取、Fact的传入和规则的执行。客户端模式是一种广泛的使用方式,规则引擎提供规则的管理和执行的算法,而真正的执行“宿主机”在业务系统这边,也就是业务系统能够轻松的把控自己的流量与规则引擎的执行算力,能够根据自身的系统负荷来调整规则引擎的执行算力。

图片

2.  服务端模式:服务端模式中,规则引擎需要扮演更加重要的角色,除了提供规则的管理和执行的算法,还需要提供规则执行的算力。服务端模式会以服务的方式提供对规则的执行,为了提供对多语言的支持,可以采用http或者grpc等跨语言的方式暴露服务。

5 规则引擎的扩展点

5.1 规则的管理

规则文件是规则引擎中很重要的一部分,它描述了具体的业务逻辑。无论是客户端模式还是服务端模式,规则执行之前的首要目标是获取到需要执行的规则文件,规则的管理是规则引擎生产化必要的建设目标,规则的管理可以从易用性、稳定性、高性能和多版本四个角度进行建设。

(1)易用性:决策表、评分卡、决策树、决策流在一定程度上简化了规则的定义,但是还是不可避免的涉及到会使用到一些类如EL的表达式,对于产品人员还是不够友好,所以从易用性角度考虑,建设一个可视化、向导式的规则管理平台很有必要。DSL领域语言+前端UI技术便是一个很好的解决方案。
DSL领域语言:它是业务人员通过dslr文件编写的业务规则,通过文字描述来映射业务规则。如下示例:

[when][]小于或者等于=<=[when][]是===[when][]年龄=age[when][]名字=name[when][]- {filed:\w*} ={field}[when]找一个学生=$p:Person()[then]学校将你安排到"{className}"=$p.setClassName("{className}")

如上所示,通过定义文字和知识库的映射关系,将晦涩的表达式转换为产品人员能够接受的中文表达式。有了知识库和文字的映射关系,通过前端技术构建一个友好的规则管理平台,即使完全不懂编程语言的人员也能够轻松的定义和管理规则。

(2)稳定性:对于一个生产级别的系统,系统的稳定性是必备的。根据系统的流量构建一个稳定的规则管理系统,流量较小的场景,可以采用主备的方式,当主节点出现问题的时候,备用节点可以很快的接管规则管理工作。当流量较大的时候,在主备模式下,除了增加硬件配置,还可以采用分片的思路,使用多节点来负载均衡流量,增加系统的稳定性。

图片

(3)高性能:规则管理平台的建设,也需要考虑到系统的性能,当系统流量本身比较固定,可以根据流量建设指定规模的管理平台,但是考虑到流量的多变,系统最好是设计成无状态可横向扩展的模式。使用分片的算法将请求的流量路由到不同的节点上,整个节点集群对外输出一个高性能的规则管理平台。

(4)多版本:在生产环境中,可能在系统发布后发现系统出现了一些潜在的bug,需要及时对系统进行回滚,规则文件也同样是这样,所以建设一个支持多版本的规则管理系统也是非常必要的。不同的企业可以考虑自身的情况选择自行架构底层的多版本框架,也可以依赖现有的例如maven仓库来实现对多版本的支持。

5.2 规则的执行状态

状态是系统建设中很重要的一环,比如在Web系统中session是一种状态,cookie也是一种状态,有了它们Web系统就能够识别到客户端是谁,从而完成系统比如鉴权、数据传递等功能。规则的执行同样需要考虑状态的问题,分为有状态和无状态。

1.  有状态:有状态是指在多次与规则引擎交互的过程中,会维护会话的状态,上一次执行中对变量的修改能够影响下一次对规则的执行。当需要清除状态的时候需要手动进行释放,在系统设计中可以利用这个特性维护一些上下文相关的规则。

2.  无状态:每一次与规则引擎的交互结束后,都会自动释放所有的状态。利用这个特性,可以方便的建设可横向扩展的规则执行引擎,类似处理无状态的Http请求。

6 规则引擎的应用场景

规则引擎适用于业务规则经常变化的一些系统中,比如风险控制系统、反欺诈系统、电商系统等,这一节我们以促销系统为例,介绍规则引擎的应用场景。

6.1 背景

某电商平台为了吸引客户会经常搞一些活动,小何常常为了做活动加班加点,心情很是浮躁。这不领导又安排一个活动:这次活动的规则是根据用户单次购买的金额赠送相应的积分,买的多送得多,具体的逻辑是用户如果单次购买金额大于100则送10积分,单次购买金额大于500则送80积分,单次购买金额大于1000则送200积分,单次购买金额大于2000则送(2.5*金额/10)积分。小何心里一想,这还不简单打开马上打开编辑工具,直接撸代码:

public int promotion(int amount) {        int num = 0;        if (amount > 100 && amount <= 500) {            num = 10;        } else if (amount > 500 && amount <= 1000) {            num = 80;        } else if (amount > 1000 && amount <= 2000) {            num = 200;        } else {            num = ((Float) (amount / 2 * 2.5f)).intValue();        }        return num;    }

撸完代码,经过测试验证,然后成功上线。上线运行一天后,运营发现情况并不理想,参与活动的人数并没有达到理想的数量,于是运营找到小何,要求把在金额条件不变的情况下积分加倍,功能刚上线一天,就算心里再不愿意,领导都提需求了,小何也只能咬咬牙,快速的改了一版代码:

public int promotion(int amount) {        int num = 0;        if (amount > 100 && amount <= 500) {            num = 20;        } else if (amount > 500 && amount <= 1000) {            num = 160;        } else if (amount > 1000 && amount <= 2000) {            num = 400;        } else {            num = ((Float) (amount * 2.5f)).intValue();        }        return num;    }

经过测试后,然后部署上线。虽然改动不大,但是完整的发布流程还是必不可少。经过这一版改动,小何心里这下可以安心的干其他活了。可是好景不长,运营又觉得改变积分后,满足条件的用户实在太多,要求把积分重新改为最初的1.5倍。小何心里想要是一直这么玩下去,自己技术水平得不到提升不说,研发激情迟早要被耗尽。

6.2 初出茅庐

有了以上的经历,小何下定决心要把这种可能变化的业务规则剥离处理,当规则有变化的时,能够独立且快速的对规则进行修改,而业务系统本身不需要进行变化,于是小何基于drl规则文件,对以上的场景进行规则定义:

rule "rule100"  no-loop true  when       $s: Order(amount >100)  then        $s.setPoints(15)       update($s)end
rule "rule500" no-loop true when $s: Order(amount >500) then $s.setPoints(120) update($s)end
rule "rule1000" no-loop true when $s: Order(amount >1000) then $s.setPoints(300) update($s)end
rule "rule2000" no-loop true when $s: Order(amount >2000) then $s.setPoints(amount/2*2.5*1.5) update($s)end

有了以上规则的独立定义,当活动由变化时,小何再也不用担心了,只需要更新规则文件,就能快速的响应需求。

6.3 精益求精

通过规则文件对可变的业务规则进行描述,确实取得了不错的效果,但是当越来越多的业务场景都是用了规则引擎,最初的架构就不足以支持大量的业务场景了。一方面规则文件需要能够更加准确的描述各种业务场景,另一方面规则引擎的稳定性和性能也是精益求精的必要建设点。

7 总结

规则引擎主要处理的是业务规则,常用于风控系统、促销系统,以及各种业务规则易变的系统,所涉及的领域是比较广泛的,但是规则引擎也不是万能的,使用它的原因是它能够让业务更加的清晰透明,能够让专业的人员更加专注的从事自己擅长的模块,能够解放生产力,实现系统的快速、低成本的迭代更新。本文站在一个规则引擎新人的角度,概要的介绍了其中的一些关键点,读者想要对规则引擎有进一步的深入了解,还需要更多的实践和积累,相信规则引擎一定能成为系统建设的一把利剑,披荆斩棘。


作  者:何信麟 (达伦)
审  稿:吴友强(技巅)
编  :周旭龙(爱迪生)


往期回顾

01
如何让你的系统快一点?
02
1024云筑极客结回顾
03
如何设计一个通用点赞系统?


图片
继续滑动看下一个
云筑网技术团队
向上滑动看下一个