0x00 目的背景

电商会员服务的等级、积分、权益等模块中,都使用了大量的规则判断。范式基本上是 达成xxx条件,执行xxx行为 。这很符合规则引擎那一套,因此下面选取了几个业界常见的规则引擎进行分析。
我们分别都从使用角度、原理角度两个方面进行分析。

0x01 easy-rules

项目地址:https://github.com/j-easy/easy-rules

1. 规则模型

比较容易搞混的概念是Rule,Rule包含了条件和行为。Condition才是用来判断true还是false的条件,Rule还包含了满足条件后需要执行的行为。

2. 如何使用

(1) 调用方式

easy-rules提供了很多种调用方式。使用方式大多都只是个范式,在官网很容易看懂。这里只列举一下各种形式。
注解方式

@Rule(name = "my rule1", description = "my rule description", priority = 1)
public class MyRule1 {@Conditionpublic boolean when(@Fact("type") Integer type) {return type == 1;}@Action(order = 1)public void execute1(Facts facts) throws Exception {log.info("MyRule1 execute1, facts={}", facts);}@Action(order = 2)public void execute2(Facts facts) throws Exception {log.info("MyRule1 execute2, facts={}", facts);}
}

流式API

Rule weatherRule = new RuleBuilder().name("weather rule").description("if it rains then take an umbrella").when(facts -> facts.get("rain").equals(true)).then(facts -> System.out.println("It rains, take an umbrella!")).build();

表达式方式
支持 MVEL , SpEL and JEXL

Rule weatherRule = new MVELRule().name("weather rule").description("if it rains then take an umbrella").when("rain == true").then("System.out.println(\"It rains, take an umbrella!\");");

规则描述文件

---
name: adult rule
description: when age is greater than 18, then mark as adult
priority: 1
condition: "person.age > 18"
actions:- "person.setAdult(true);"
---
name: weather rule
description: when it rains, then take an umbrella
priority: 2
condition: "rain == true"
actions:- "System.out.println(\"It rains, take an umbrella!\");"

(2) 规则组合

规则的组合,和规则之间的逻辑关系。
在easy-rules中,规则可以被组合成为 CompositeRule ,并且提供了三种逻辑关系:

  • UnitRuleGroup:规则之间是 AND 的关系,所有规则是一个整体,要么应用所有规则,要么不应用任何规则
  • ActivationRuleGroup:规则之间是 XOR 的关系,只会执行第一个命中的规则,其它规则均忽略
  • ConditionalRuleGroup:具有最高优先级的规则作为 触发条件 ,如果满足触发条件,则会继续执行其它规则

(3) 规则引擎

easy-rules提供了两种规则引擎实现:

  • DefaultRulesEngine:根据规则的自然顺序(默认为优先级)应用规则。
  • InferenceRulesEngine:持续对已知事实应用规则,直到不再应用规则为止。

3. 原理浅析

我们从Rule看起,了解规则整体模型。然后再看RuleEngine,了解规则加载、判断和执行流程。

(1) Rule

Rule是可比较的,这里主要是方便使用优先级进行排序。Rule是个接口,BasicRule相当于一个Adapter,所有其他Rule都继承BaseRule。

 public interface Rule extends Comparable<Rule> {// 省略部分描述和标识性方法/*** 用于判断Condition是否满足*/boolean evaluate(Facts facts);/*** 用于执行Actions* @throws 执行action(s)时如果发生错误会抛出异常*/void execute(Facts facts) throws Exception;}

来看一下DefaultRule的实现会更清晰:

class DefaultRule extends BasicRule {private final Condition condition;private final List<Action> actions;// 省略构造方法...@Overridepublic boolean evaluate(Facts facts) {return condition.evaluate(facts);}@Overridepublic void execute(Facts facts) throws Exception {for (Action action : actions) {action.execute(facts);}}
}

所以,条件判断、行为执行都是Condition和Action自己做的。我们可以跟进去看看。

(2) Condition

Condition就是需要由用户实现的条件接口,实现evaluate即可。easy-rules提供了表达式实现,下面章节会简要分析。

(3) Action

Action就是需要由用户实现的条件接口,实现execute即可。easy-rules提供了表达式实现,下面章节会简要分析。

(4) RulesEngine

RuleEngine有两种实现,我们以DefaultRulesEngine来分析整体流程。
抛开那些Listener不谈,RulesEngine有两个主要方法:

public interface RulesEngine {/*** 使用facts执行所有已经注册的rules*/void fire(Rules rules, Facts facts);/*** 只检查规则,但是不执行Action,其实就是调用每个Rule的evaluate* @return 每个Rule对应的执行结果*/default Map<Rule, Boolean> check(Rules rules, Facts facts) {return Collections.emptyMap();}
}

来看看DefaultRulesEngine#doFire

// 已经省略了部分不重要的代码
void doFire(Rules rules, Facts facts) {if (rules.isEmpty()) {return;}LOGGER.debug("Rules evaluation started");for (Rule rule : rules) {final String name = rule.getName();final int priority = rule.getPriority();boolean evaluationResult = false;try {evaluationResult = rule.evaluate(facts);} catch (RuntimeException exception) {// 异常处理}if (evaluationResult) {LOGGER.debug("Rule '{}' triggered", name);try {rule.execute(facts);LOGGER.debug("Rule '{}' performed successfully", name);} catch (Exception exception) {// 异常处理}} else {// 异常处理}}
}

(5) 表达式

可以看出,easy-rules的条件判断和Action执行,都是基于 表达式引擎 来实现的,是重度依赖表达式引擎的。不过说到底,规则判断可不就是表达式计算么。下面以SpEL为例分析:

@Override
public boolean evaluate(Facts facts) {StandardEvaluationContext context = new StandardEvaluationContext();// 实际值转换为map作为表达式变量context.setRootObject(facts.asMap());context.setVariables(facts.asMap());if (beanResolver != null) {context.setBeanResolver(beanResolver);}// 执行表达式return compiledExpression.getValue(context, Boolean.class);
}

@Override
public void execute(Facts facts) {try {StandardEvaluationContext context = new StandardEvaluationContext();context.setRootObject(facts.asMap());context.setVariables(facts.asMap());if (beanResolver != null) {context.setBeanResolver(beanResolver);}compiledExpression.getValue(context);} catch (Exception e) {LOGGER.error("Unable to evaluate expression: '" + expression + "' on facts: " + facts, e);throw e;}
}

在提供Action和Condition的时候,实际上就是直接提供对应的表达式语句即可,入参都是String类型的。

/*** Specify the rule's condition as SpEL expression.* @param condition of the rule* @return this rule*/
public SpELRule when(String condition) {this.condition = new SpELCondition(condition, parserContext, beanResolver);return this;
}/*** Add an action specified as an SpEL expression to the rule.* @param action to add to the rule* @return this rule*/
public SpELRule then(String action) {this.actions.add(new SpELAction(action, parserContext, beanResolver));return this;
}

注意condition表达式和action表达式是分开存储的,所以上面evaluate和execute都是在执行表达式,但是执行的内容却是不同的。

4. 总结

easy-rules的代码结构设计的很清晰,代码也很清爽。项目本身很轻量级,基本上只提供了一个规则判断和行为执行的框架,相当于是对计算过程的抽象。
但也正是因为轻量级,easy-rules几乎不包含规则编排等功能,如果规则的条件本身是很复杂的,那么我们只能自己对这些条件进行编排,对于easy-rules来说,它只是一条单一的规则。当然,你也可以借助EL表达式的力量,实现一些线性编排。

0x02 Drools

项目官网:https://www.drools.org/
源码:https://github.com/kiegroup/drools
Drools是一个绝对重量级的规则引擎,很多像金融行业、电信行业的大公司都在使用它作为规则引擎。

1. 规则模型

  • 事实(Fact):对象之间及对象属性之间的关系
  • 规则(rule):是由条件和结论构成的推理语句,一般表示为if…then。一个规则的if部分称为LHS,then部分称为RHS。
  • 模式(module):就是指IF语句的条件。这里IF条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。

2. RETE算法

Rete 算法最初是由卡内基梅隆大学的 Charles L.Forgy 博士在 1974 年发表的论文中所阐述的算法 , 该算法提供了专家系统的一个高效实现。自 Rete 算法提出以后 , 它就被用到一些大型的规则系统中 , 像 ILog、Jess、JBoss Rules 等都是基于 RETE 算法的规则引擎。
Forgy的论文原文:RETE Match Algorithm - Forgy OCR.pdf

Rete 在拉丁语中译为”net”,即网络。Rete 匹配算法是一种进行大量模式集合和大量对象集合间比较的高效方法,通过网络筛选的方法找出所有匹配各个模式的对象和规则。

其核心思想是将分离的匹配项根据内容动态构造匹配树,以达到显著降低计算量的效果。Rete 算法可以被分为两个部分:规则编译和规则执行。当Rete算法进行事实的断言时,包含三个阶段:匹配、选择和执行,称做 match-select-act cycle。

我们先来了解一些基础概念,下图是一个基本上包含了所有常见元素的RETE网络:

我将RETE网络中的元素整理如下:

RETE算法就是Drools引擎执行速度快的秘诀。个人认为,RETE算法有以下特点:

  • 不同规则之间的相同模式是可以共享节点和存储区的,所以做过的判断不需要重复执行,是典型的空间换时间的做法
  • 使用AlphaMemory和BetaMemory存储事实,当事实部分变化时,可以只计算变化的事实,提高了匹配效率
  • 事实只有满足当前节点,才会向下传递。不做无意义的匹配。

RETE算法的不足:
因为RETE是空间换时间,所以当规则和事实很多的时候,可能会耗尽系统资源。

举例,平台有如下规则:

  • 付费会员生日当天购物满1000元,可获得礼包A
  • 付费会员生日当天购物满500元,可获得礼包B
  • 付费会员生日当天购物满100元,可获得礼包C

这些规则建立的RETE网络大概是:

RETE网络的创建流程:

  1. 创建根节点
  2. 加入一条规则
    1. 取出规则中的一个模式(模式就是规则中的最小一个匹配项例如: age>10age<20 ),检查模式中的参数类型,如果是新类型(也就是新的Fact类型),则加入一个类型节点;
    2. 检查模式对应的 Alpha 节点是否已存在,如果存在则记录下节点位置,如果没有则将模式作为一个 Alpha 节点加入到网络中,同时根据 Alpha 节点的模式建立 Alpha Memory;
    3. 重复 b 直到所有的模式处理完毕;
    4. 组合 Beta 节点,按照如下方式: Beta 左输入节点为 Alpha(1),右输入节点为 Alpha(2) 。Beta(i) 左输入节点为 Beta(i-1),右输入节点为 Alpha(i) i>2 并将两个父节点的内存表内联成为自己的内存表;
    5. 重复 d 直到所有的 Beta 节点处理完毕;
    6. 将动作(Then 部分)封装成Beta(n) 的输出节点;
  3. 重复2直到所有规则处理完毕

RETE如何处理模式之间的or关系?由于模式之间只有NOT和EXIST两种关系,Beta Node并不能表述or的关系,所以Drools将其拆分成两条规则来看待。

3. PHREAK算法

PHREAK是由Drools团队设计和实现的一种算法,并在Drools 6中引入。PHREAK是一种慵懒匹配算法,在模型上基本延续了RETE,节点及其作用都是一样的。

如果你想了解性能差异,这里有一篇PHREAK和RATE算法的性能比较结果。
http://ksoong.org/drools-examples/content/docs/phreak.html#_phreak_vs_rateoo

PHREAK算法在RETE之上的改进点:

  • 延迟规则评估 :当PHREAK引擎启动后,所有规则都处于一种 unlinked 的状态,这种状态的规则不会被Drools执行。当 insertupdatedelete 等操作修改了KIE会话的状态时,修改只会传播到alpha子网,并在进入beta子网之前排队。与RETEOO不同,在PHREAK中,不会执行Beta节点以用作这些操作的结果。 引擎会先用试探法确定哪个规则最有可能导致匹配,从而在它们之间强加一个执行顺序。
  • 面向集合传播 :在RETEOO中,每次insert/update/delete事实时,都会从顶部(入口点)到底部(最佳情况下的规则终端节点)遍历网络。网络中执行评估的每个节点都创建了一个元组,该元组传播到路径中的下一个节点。PHREAK不是这样工作的。一个Beta节点上所有排队的insert/update/delete操作会被批处理评估,并且结果会放到一个Set中。这个Set会被转发到下一个节点,并且执行上面同样形式的评估,并把结果放到同一个Set中。面向集合传播在某些规则上具有性能优势,并且为将来做多线程评估提供了可能。
  • 分割网络 :在分段中,一个KIE Base中的节点是可以在不同规则之家共享的。PHREAK将规则是为分段的路径,而不是节点的路径。一个不与其他任何规则共享其节点的规则由单个段组成。路径中每个段都分配有1 bit的标志位。当节点包含足够评估的数据时,该标志位设置为on。当段中所有节点都为on时,段本身将设置为on。当规则的所有段都为on时,该规则会被标记为 linked 。Drools利用这些标志位来避免对已经评估的节点和段进行重新评估,从而让PHREAK网络的评估效率更高。

想了解更多关于PHREAK的新消息,可参考:http://blog.athico.com/2015/12/drools-detailed-description-of-internal.html

4. 总结

Drools发展到今天,其实已经是一整套解决方案了。
如果只是想要简单使用,那就是只用到Drools的核心BRE,引入几个Maven依赖,编写Java代码和规则文件即可。但是如果要编排很复杂的工程,甚至整个业务都重度依赖,需要产品、运营同学一起来指定规则,则需要用到BRMS整套解决方案了,包括Drools Expert(BRE)、Drools Workbench、DMN。
所以我们说Drools太重了,主要是在说:

  1. Drools相关的组件太多,需要逐个研究才知道是否需要
  2. Drools逻辑复杂,不了解原理,一旦出现问题排查难度高
  3. Drools需要编写规则文件,学习成本高

这次分析没有看太多Drools的源码,与easy-rules相比,代码复杂度也不是一个数量级上面的。后面可以再开专题去聊一下Drools的源码,对深入理解RETE或者PHREAK也会有很大帮助。

0x03 总结

时间原因,只找了两个最典型的规则引擎项目进行分析,其他像Urule也是用的RETE算法,与Drools的原理大同小异。还有一些轻量级的规则引擎项目,大多也都是简化了规则编排的能力,并依赖表达式引擎做规则判断。

easy-rules Drools
学习成本
维护成本
规则编排能力 较弱
执行效率 较低
是否开源 开源 开源

0x04 参考资料

  1. [wiki]《Rete algorithm》
  2. [博客]《drools -Rete算法》,by 双斜杠少年
  3. [博客]《RETE算法简述 & 实践》,by RyanLee_
  4. [论文]《Rete: A Fast Algorithm for the Many Pattern/Many Object Pattern Match Problem》,by Charles, Forgy
  5. [书籍]《Mastering JBoss Drools 6 for Developers》,by Mauricio Salatino
  6. [代码] github:easy-rules
  7. [代码] github:drools

常见开源规则引擎对比分析相关推荐

  1. 流媒体服务器(3)—— 常见开源流媒体服务器对比分析

    目录 前言 正文 SRS 使用步骤 主要功能 EasyDarwin 使用步骤

  2. 常见的规则引擎(Drools,RuleBook,Easy Rules等)对比

    参考文章: https://www.jianshu.com/p/96cd60059aae 规则引擎调研 - 人在江湖之诗和远方 - 博客园 java开源规则引擎比较_常用规则引擎比较分析_学校砍了我的 ...

  3. java开源规则引擎比较_规则引擎对比

    RuleEngine--一款使用简单,入门方便的数据库规则引擎_Rewen的专栏-CSDN博客_rulesengine https://blog.csdn.net/Rewen/article/deta ...

  4. 开源OLAP引擎对比

    文章目录 开源OLAP引擎对比 OLAP简介 分布式OLAP引擎分类及对比 基于MPP架构的ROLAP引擎 预计算引擎架构的MOLAP 搜索引擎架构 纯列存OLAP 基于内存的SnappyData 对 ...

  5. 开源规则引擎 drools

    前言 在很多企业的 IT 业务系统中,经常会有大量的业务规则配置,而且随着企业管理者的决策变化,这些业务规则也会随之发生更改.为了适应这样的需求,我们的 IT 业务系统应该能快速且低成本的更新.适应这 ...

  6. 开源规则引擎——ice:致力于解决灵活繁复的硬编码问题

    背景介绍 业务中是否写了大量的 if-else?是否受够了这些 if-else 还要经常变动? 业务中是否做了大量抽象,发现新的业务场景还是用不上? 是否各种调研规则引擎,发现不是太重就是接入或维护太 ...

  7. 阿里开源规则引擎QLExpress-入门实战

    介绍 规则引擎,顾名思义是针对我们业务系统中普世的规则进行统一管理,通过该引擎进行调度计算,可以动态调整规则的表达式内容,而不影响业务系统代码,常见的业务典型场景有电商中促销活动,单品折扣.整场活动满 ...

  8. 注册中心—常见注册中心组件对比分析

    原文作者:知了V笑 原文地址:微服务技术栈:常见注册中心组件,对比分析 关于注册中心原理,请先阅读微服务注册中心原理 目录 一.Zookeeper组件 二.Eureka组件 三.Consul组件 四. ...

  9. Java三大主流开源工作流引擎技术分析

    首先,这个评论是我从网上,书中,搜索和整理出来的,也许有技术点上的错误点,也许理解没那么深入.但是我是秉着学习的态度加以评论,学习,希望对大家有用,进入正题! 三大主流工作流引擎:Shark,oswo ...

  10. 三大主流开源工作流引擎技术分析与市场预测

    1.从<功夫>说起 时下的新新人类看到我,一定会认为在下是个十足的老古董,这不,<功夫>这样的片子我到今年2月底才看.不过看过<功夫>,我想的一定比一般的人多:周星 ...

最新文章

  1. ST IKS01A1 驱动程序分析
  2. Nature科学报告:这项研究,有助于截肢患者可以恢复痛觉感知
  3. NLP:GLUE和SuperGLUE基准的简介、任务分类、使用方法之详细攻略
  4. MOVE-CORRESPONDING 应该注意的语法特点
  5. Hello boke!
  6. 神奇的[Caller*]属性
  7. 总线接口与计算机通信
  8. 数字图像的大小、所需比特数(二维)
  9. matlab 类 继承,继承MATLAB中的密封类
  10. chroot函数使用_PHP chroot()函数与示例
  11. java jsp session_JSP中Session的使用
  12. android AVB2.0学习总结
  13. Java作业-多线程
  14. 查找树的指定层级_非递归层次遍历方法实现二叉树中指定节点的层次数查找
  15. Myeclipse错误:Errors occurred during the build. 解决方法
  16. indesign排版标点挤压_indesign排版标点挤压设置技巧
  17. 微信小程序 时间插件 (可以选择日期+星期)
  18. SSR for mac with free download addresses
  19. Java使用RXTX读取串口
  20. exchange虚拟服务器,exchange服务器之为Exchange服务重建IIS虚拟目录

热门文章

  1. Google Chrome 怎么下载离线安装包
  2. 伸缩盒header固定content变更,footer固定
  3. light动名词_英语里有些动词有名词形式,那还用不用它的动名词?怎么区分?...
  4. Grafana启动失败报错:Grafana-server Init Failed: Could not find config defaults, make sure homepath command
  5. Ubuntu18.04 笔记本电脑使用USB摄像头替代自带摄像头 安装摄像头驱动usb_cam的方法
  6. 《HelloGitHub》第 67 期
  7. 趣学Spring:一文搞懂Aware、异步编程、计划任务
  8. 教你Word一键自动生成目录步骤
  9. 信息化知识中的重点:商业智能(BI)详解
  10. toft 测试用例rat_测试案例如何区分RAT,FAST,TOFT,FET | 学步园