概述
本文主要介绍一些常见的规则引擎和以及一些规则引擎的简单使用;
以及rete算法的一些见解。
规则引擎简介
•规则引擎是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来。
• 规则引擎的核心就是获取knowledge(知识)。
• 应用knowledge到特定的数据上(fact)。
• 使用 “production rules(产生式规则)” IF THEN Rule表达逻辑(任何逻辑都可以用这种方式表达)。
什么是规则
• 一个rule由conditions,和actions组成。
• 当所有的conditions匹配,rule可能“fire” Conditions即LHS(left hand side)Actions即RHS(right hand side或者consequence)
• Rule操纵应用程序中的数据( fact )
• Rule engines(比如Drools)使用正向或者反向链(或者混合使用)
• 正向链从事实到结论的推理。rule在LHS conditions匹配的时候执行。Actions可以改变facts,并可能导致新rule被fire。
• 反向链指则是从假设,即要证明的结论,到事实的推理。
有哪些开源的JAVA规则引擎
Drools、Easy Rules、Mandarax、IBM ILOG, 使用最为广泛并且开源的是Drools。
IBM ILOG
IBM旗下的规则引擎,是要收费的可获取免费版本,可结合eclipse等开发工具在可视化界面操作和配置规则等,但流程比较复杂。
使用ILOG 业务规则管理系统可让应用程序更具灵活性。ILOG 业务规则引擎可在整个企业内部署和应用策略变化。您可根据客户需求、规章变化和竞争情况迅速做出反应。 优化研发最好的规划和计划,以使服务、收入和利润最大化。ILOG 优化技术可处理任意数目的运营约束。帮您赢得自信,运筹帷幄,在正确的时间做出正确的决定,从而降低风险。 可视化使用 ILOG 可视化技术生动地显示复杂的数据。ILOG 可视化勾画出总体形势,把原始数据转换成有用的信息。可以通过行动信息的实时图像迅速获得数据。
Easy rules 介绍
Easy rules是一款轻量级的规则引擎,在github上已经开源, 社区活跃度比较高。
• 轻量级框架和易于学习的API
• 基于POJO的开发
• 有用的抽象来定义业务规则并轻松应用它们
• 能够从原始规则创建复合规则
• 能够在表达式语言中定义规则
• 在3.0版本后已经是线程安全
Easy rules 使用
定义规则
定义规则引擎
定义规则监听
规则示例-shop,这是一个模拟网上商城拒绝卖酒给未成年的规则.
Mandarax
• Mandarax是一个规则引擎的纯Java实现,基于反向推理(归纳法)。能够较容易地实现多个数据源的集成。例如,数据库记录能方便地集成为事实集 (facts sets),reflection用来集成对象模型中的功能。支持XML标准(RuleML 0.8)。它提供了一个兼容J2EE的使用反向链接的接口引擎。目前不支持JSR 94。
• 使用率很少,文档较少。
JBOSS Drools
JBOSS DROOLS是一个业务规则管理系统,具有基于前向链接和后向链接推理的规则引擎,允许快速和可靠地评估业务规则和复杂事件处理。
规则引擎也是创建专家系统的基本构件,在人工智能中,专家系统是模拟人类专家的决策能力的计算机系统。
Drools优点:
• 非常活跃的社区支持
• 易用
• 快速的执行速度
• 在 Java 开发人员中流行
• 与 Java Rule Engine API(JSR 94)兼容
Drools示例
定义drl文件
Drools语法简介
1 规则语言:一个规则通常包括三个部分:属性部分(attribute)、条件部分(LHS)和结果部分(RHS)。
2条件部分LHS
条件部分又被称之为Left Hand Side,简称为LHS,下文当中,如果没有特别指出,那么所说的LHS 均指规则的条件部分,在一个规则当中when 与then 中间的部分就是LHS 部分。在LHS 当中,可以包含0~n 个条件,如果LHS 部分没空的话,那么引擎会自动添加一个eval(true)的条件,由于该条件总是返回true,所以LHS 为空的规则总是返回true。LHS 部分是由一个或多个条件组成,条件又称之为pattern(匹配模式),多个pattern之间用可以使用and 或or 来进行连接,同时还可以使用小括号来确定pattern 的优先级。
对于一个pattern 来说“绑定变量名”是可选的,如果在当前规则的LHS 部分的其它的pattern 要用到这个对象,那么可以通过为该对象设定一个绑定变量名来实现对其引用,对于绑定变量的命名,通常的作法是为其添加一个“$”符号作为前缀,这样可以很好的与Fact的属性区别开来;绑定变量不仅可以用在对象上,也可以用在对象的属性上面,命名方法与对象的命名方法相同;“field 约束”是指当前对象里相关字段的条件限制
示例如下:
此段规则的含义为:的规则就包含两个pattern,第一个pattern 有三个约束,分别是:对象类型必须是Cutomer;同时Cutomer 的age 要大于20 且gender 要是male;
第二个pattern 也有三个约束,分别是:对象类型必须是Order,同时Order 对应的Cutomer 必须是前面的那个Customer 且当前这个Order 的price 要大于1000。
在这两个pattern 没有符号连接,在Drools当中在pattern 中没有连接符号,那么就用and 来作为默认连接,所以在该规则的LHS 部分中两个pattern 只有都满足了才会返回true。
默认情况下,每行可以用“;”来作为结束符(和Java 的结束一样),当然行尾也可以不加“;”结尾。
3约束连接
对于对象内部的多个约束的连接,可以采用“&&”(and)、“||”(or)和“,”(and)来实现,“&&”(and)、“||”(or)和“,”这三个连接符号如果没有用小括号来显示的定义优先级的话,那么它们的执行顺序是:“&&”(and)、“||”(or)和“,” “&&”优先级最高,表面上看“,”与“&&”具有相同的含义,但是有一点需要注意,“,”与“&&”和“||”不能混合使用,也就是说在有“&&”或“||”出现的LHS 当中,是不可以有“,”连接符出现的,反之亦然。
4比较操作符
在当中共提供了十二种类型的比较操作符,分别是:>、>=、<、<=、= =、!=、contains、not contains、memberof、not memberof、matches、not matches;在这十二种类型的比较操作符当中,前六个是比较常见也是用的比较多的比较操作符。
5属性:
规则属性是用来控制规则执行的重要工具,在规则的属性共有13 个,它们分别是:activation-group、agenda-group、auto-focus、date-effective、date-expires、dialect、duration、enabled、lock-on-active、no-loop、ruleflow-group、salience、when
Drools代码演示
pom.xml 添加依赖
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>6.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>6.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.2</version>
</dependency>
</dependencies>
规则实体类
package com.neo.drools.model;
import org.apache.commons.lang3.builder.ToStringBuilder;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.io.Serializable;
@Entity
public class Rule implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, unique = true)
private String ruleKey;
@Column(nullable = false)
private String content;
@Column(nullable = false, unique = true)
private String version;
@Column(nullable = true, unique = true)
private String lastModifyTime;
@Column(nullable = false)
private String createTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRuleKey() {
return ruleKey;
}
public void setRuleKey(String ruleKey) {
this.ruleKey = ruleKey;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getLastModifyTime() {
return lastModifyTime;
}
public void setLastModifyTime(String lastModifyTime) {
this.lastModifyTime = lastModifyTime;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
初始化KieContainer-by drl file
package com.neo.drools.config;
import com.neo.drools.service.ReloadDroolsRulesService;
import org.drools.core.io.impl.ClassPathResource;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.kie.spring.KModuleBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;
@Configuration
public class DroolsAutoConfiguration {
private static final String RULES_PATH = "rules/";
@Bean
@ConditionalOnMissingBean(KieFileSystem.class)
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
Resource [] resourceArray = getRuleFiles();//获取文件并加载
for (Resource file : resourceArray) {
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
}
return kieFileSystem;
}
private Resource[] getRuleFiles() throws IOException {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources(RULES_PATH + "**/*.drl");
}
@Bean
@ConditionalOnMissingBean(KieContainer.class)
public KieContainer kieContainer() throws IOException {
final KieRepository kieRepository = getKieServices().getRepository();
kieRepository.addKieModule(new KieModule() {
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
KieFileSystem kieFileSystem = kieFileSystem();
KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
KieContainer kieContainer=getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
ReloadDroolsRulesService.kieContainer = kieContainer;
return kieContainer;
}
private KieServices getKieServices() {
return KieServices.Factory.get();
}
@Bean
@ConditionalOnMissingBean(KieBase.class)
public KieBase kieBase() throws IOException {
return kieContainer().getKieBase();
}
@Bean
@ConditionalOnMissingBean(KieSession.class)
public KieSession kieSession() throws IOException {
return kieContainer().newKieSession();
}
@Bean
@ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
public KModuleBeanFactoryPostProcessor kiePostProcessor() {
return new KModuleBeanFactoryPostProcessor();
}
}
初始化KieContainer by db rules
package com.neo.drools.service;
import com.neo.drools.model.Rule;
import com.neo.drools.repository.RuleRepository;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.Message;
import org.kie.api.runtime.KieContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by neo on 17/7/31.
*/
@Service
public class ReloadDroolsRulesService {
public static KieContainer kieContainer;
@Autowired
private RuleRepository ruleRepository;
public void reload(){
KieContainer kieContainer=loadContainerFromString(loadRules());
this.kieContainer = kieContainer;
}
private List<Rule> loadRules(){
List<Rule> rules=ruleRepository.findAll();
return rules;
}
private KieContainer loadContainerFromString(List<Rule> rules) {
long startTime = System.currentTimeMillis();
KieServices ks = KieServices.Factory.get();
KieRepository kr = ks.getRepository();
KieFileSystem kfs = ks.newKieFileSystem();
for (Rule rule:rules) {
String drl=rule.getContent();
kfs.write("src/main/resources/" + drl.hashCode() + ".drl", drl);
}
KieBuilder kb = ks.newKieBuilder(kfs);
kb.buildAll();
if (kb.getResults().hasMessages(Message.Level.ERROR)) {
throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
}
long endTime = System.currentTimeMillis();
System.out.println("Time to build rules : " + (endTime - startTime) + " ms" );
startTime = System.currentTimeMillis();
KieContainer kContainer = ks.newKieContainer(kr.getDefaultReleaseId());
endTime = System.currentTimeMillis();
System.out.println("Time to load container: " + (endTime - startTime) + " ms" );
return kContainer;
}
}
Web controller
package com.neo.drools.controller;
import com.neo.drools.model.Address;
import com.neo.drools.model.fact.AddressCheckResult;
import com.neo.drools.service.ReloadDroolsRulesService;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.io.IOException;
@RequestMapping("/test")
@Controller
public class TestController {
@Resource
private ReloadDroolsRulesService rules;
@ResponseBody
@RequestMapping("/address")
public void test(){
Address address = new Address();
address.setPostcode(generateRandom(10));
KieSession kieSession = ReloadDroolsRulesService.kieContainer.newKieSession();
AddressCheckResult result = new AddressCheckResult();
kieSession.insert(address);
kieSession.insert(result);
int ruleFiredCount = kieSession.fireAllRules();
kieSession.destroy();
System.out.println("触发了" + ruleFiredCount + "条规则");
if(result.isPostCodeResult()){
System.out.println("规则校验通过");
}
}
/**
* 从数据加载最新规则
* @return
* @throws IOException
*/
@ResponseBody
@RequestMapping("/reload")
public String reload() throws IOException {
rules.reload();
return "ok";
}
/**
* 生成随机数
* @param num
* @return
*/
public String generateRandom(int num) {
String chars = "0123456789";
StringBuffer number=new StringBuffer();
for (int i = 0; i < num; i++) {
int rand = (int) (Math.random() * 10);
number=number.append(chars.charAt(rand));
}
return "hello";
}
}
数据库DRL数据
下面是其中的一条数据中的content中的数据
package plausibcheck.adress
import com.neo.drools.model.Address;
import com.neo.drools.model.fact.AddressCheckResult;
rule "Postcode test999"
when
address : Address(postcode == "hello")
checkResult : AddressCheckResult();
then
checkResult.setPostCodeResult(true);
System.out.println("规则中打印日志:校验通过4444!");
end
结果
1.初始化时通过文件加载KieContainer,调用时触发了默认的规则.
2.调用接口重新reload KieContainer
3.再次调用发现触发了两条规则,但这两条规则是数据库中的RULE触发的,因为KieContainer只使用了数据库的RULE,并没有使用原来的文件中的rule.
Drools热更新
热更新只需要重新初始化KieContainer就OK,新的规则即可生效.
rete 算法
Rete 算法最初是由卡内基梅隆大学的 Charles L.Forgy 博士在 1974 年发表的论文中所阐述的算法 , 该算法提供了专家系统的一个高效实现。自 Rete 算法提出以后 , 它就被用到一些大型的规则系统中 , 像 ILog、Jess、JBoss Rules 等都是基于 RETE 算法的规则引擎.
Rete 在拉丁语中译为”net”,即网络。Rete 匹配算法是一种进行大量模式集合和大量对象集合间比较的高效方法,通过网络筛选的方法找出所有匹配各个模式的对象和规则。
其核心思想是将分离的匹配项根据内容动态构造匹配树,以达到显著降低计算量的效果。Rete 算法可以被分为两个部分:规则编译和规则执行。
RETE算法说明
节点说明
规则示例
1.从工作内存中取一工作存储区元素WME(Working Memory Element,简称WME)放入根节点进行匹配。WME是为事实建立的元素,是用于和非根结点代表的模式进行匹配的元素。
(1建立存储元素)
2.遍历每个alpha节点(含ObjectType节点),如果alpha节点约束条件与该WME一致,则将该WME存在该alpha节点的匹配内存中,并向其后继节点传播。
(遍历每个红色代表的plpha节点,并存入内存中供使用)
3.对alpha节点的后继结点继续(2)的过程,直到alpha内存所有通过匹配的事实保存在alpha内存中。
(重复2的过程)
4.对每个beta节点进行匹配,如果单个事实进入beta节点左部,则转换成一个元素的元组存在节点左侧内存中。如果是一个元组进入左部,则将其存在左内存中。如果一个事实进入右侧,则将其与左内存中的元组按照节点约束进行匹配,符合条件则将该事实对象与左部元组合并,并传递到下一节点。bata结点有left存储区和right存储,其中left存储区是beta内存,right存储区是alpha内存。存储区储存的最小单位是WME。
(匹配蓝色等变形代表的beta节点,相当于是构造内存)
5.重复(4)直到所有beta处理完毕,元组对象进入到Terminal节点。对应的规则被触活,将规则后件加入议程(Agenda)。
(处理所有beta节点直到terminal节点,将激活规则加入到agenda中)
6.对Agenda里的规则进行冲突消解,选择合适的规则执行。
(agenda解决规则冲突,选择合适的规则执行)
Rete算法的特点
rete算法通过共享规则节点和缓存匹配结果,获得产生式推理系统在时间和空间上的性能提升。
1、状态保存
事实集合中的每次变化,其匹配后的状态都被保存再alpha和beta节点中。在下一次事实集合发生变化时,绝大多数的结果都不需要变化,rete算法通过保存操作过程中的状态,避免了大量的重复计算。Rete算法主要是为那些事实集合变化不大的系统设计的,当每次事实集合的变化非常剧烈时,rete的状态保存算法效果并不理想。
2、节点共享
另一个特点就是不同规则之间含有相同的模式,从而可以共享同一个节点。Rete网络的各个部分包含各种不同的节点共享。
3、节点索引
索引方法是指对rete网络的节点建立当前节点对后继的索引,在事实断言时可以通过索引快速找到对应的后继节点而无需逐个查找。drools在rete的面向对象版本rete-oo算法中对ObjectType节点增加后继alpha节点的索引,以事实的属性为key,alpha节点为value,这样在事实通过类型节点验证后可以迅速找到对应的alpha节点进行断言。
同样,对beta节点也可以建立索引,beta节点的索引主要是针对节点左右内存的查询。当一个事实传递到beta节点的右内存中时,需要与该节点的左内存进行连接操作,即遍历左侧内存中的事实元组,找到符合节点约束的事实进行连接。该过程的遍历查找效率较低,将beta内存分成若干单元,每个单元分配一个id;对右侧的事实用哈希函数求索引,该索引就是某个单元的位置,通过索引快速找到相应单元进行匹配,如果不在该分区,则将该对象组成一个新的单元加入左内存。
4 Rete 算法的不足
存在状态重复保存的问题,比如匹配过模式1和模式2的事实要同时保存在模式1和模式2的节点缓存中,将占用较多空间并影响匹配效率。
事实的删除与事实的添加顺序相同, 除了要执行与事实添加相同的计算外, 还需要执行查找, 开销很高。
5.rete的一个主要缺点就是不适合处理快速变化的数据和规则。
主要表现在:
数据变化引起节点保存的临时事实频繁变化,这将让rete失去增量匹配的优势。
数据的变化使得对规则网络的种种优化方法如索引、条件排序等失去效果。
rete算法使用了alpha内存存储已计算的中间结果, 以牺牲空间换取时间, 从而加快系统的速度。然而当处理海量数据与规则时,beta内存根据规则的条件与事实的数目而成指数级增长, 所以当规则与事实很多时, 会耗尽系统资源。
规则引擎不能处理缺失的数据及模糊的逻辑。例如规则“如果年级大则容易患中风”。产生式推理系统将不能精确表达“年级大”及“容易”这样的概念,相应的推理也不能得到精确的结果。这种场合下,算法变得很脆弱,有点儿类似AI的一部分功能,所以drools通常被称作伪人工智能。
一盏灯, 一片昏黄; 一简书, 一杯淡茶。 守着那一份淡定, 品读属于自己的寂寞。 保持淡定, 才能欣赏到最美丽的风景! 保持淡定, 人生从此不再寂寞。