0x00 开始
Drools是JBoss旗下的一款开源规则引擎。
规则引擎可以实现业务规则和代码的分离,使得非技术人员也可以配置业务规则,使业务规则有更强的可维护性。而Drools是开源的规则引擎中使用最广泛的。
目前Drools的最高版本是7.0.0beta,网上大多数例子都是以Drools 5.x为例,而中文的Drools 6.x 例子较少,在本文中已Drools 6.4.0.Final为例,只使用Drools 6.x的新API。
官方文档地址:
https://docs.jboss.org/drools/release/6.4.0.Final/drools-docs/html_single/
Drools 6.0对5.x的主要改进有:
1. Kie API
整合了jBPM
、UberFire
等等一票项目,Kie
的意思是Knowledge is everything
。
2. 利用maven repo管理规则,分离系统和规则代码,根据maven版本更新规则。
3. PHREAK
算法,官方文档认为其改善了多线程下的性能
4. 支持定时器表达式
0x01 例子
由于Drools 6.0 和maven高度耦合,所以建议项目都使用maven管理。
规则项目
pom.xml
<groupId>org.drools.test</groupId>
<artifactId>artifact-rule</artifactId>
<version>1.0</version>
Drools的规则预发并没有什么变化,这里只演示最简单的规则
放在src/main/resources/pkg/rule.drl
import drools.Bean;
rule "Len0"
when
m : Bean( value == 0 )
then
System.out.println("Hello World 0");
end
# ......
rule "Len999"
when
m : Bean( value == 999 )
then
System.out.println("Hello World 999");
end
定义KieModulesrc/main/resources/META-INF/kmodule.xml
,注意rule、pkg、first,这必须和其他配置匹配。
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
<kbase name="rule" packages="pkg">
<ksession name="first" />
</kbase>
</kmodule>
主项目
maven依赖:
<!-- 不需要再关心每个Drools组件的版本了 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<type>pom</type>
<version>6.4.0.Final</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 引入规则项目 -->
<dependency>
<groupId>org.drools.test</groupId>
<artifactId>artifact-rule</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
Bean文件:src/main/java/drools/Bean.java
package drools;
public class Bean {
private Integer value; // 唯一的属性
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
测试文件:
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
// 从默认的kmodule.xml路径读取KieModule配置,然后根据kmodule.xml中的定义查找drl文件
KieSession kSession = kContainer.newKieSession("first");
for (int i = 0; i < 100; ++i) {
Bean bean = new Bean();
bean.setValue(i);
kSession.insert(bean);
kSession.fireAllRules();
}
0x02 无状态Session
Drools 6.x 文档中将PHREAK
算法描述为一个惰性规则匹配算法,当有多个线程并发请求Drools执行规则时,只会有一个线程会执行,而其他线程在kSession.fireAllRules();
会直接返回,而不做任何执行(该方法返回执行的Fact数目,所以此时返回0)。
所以如果需要一个同步的规则匹配并执行,则需要使用无状态的Session。
修改src/main/resources/META-INF/kmodule.xml
,type="stateless"
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
<kbase name="rule" packages="pkg">
<ksession type="stateless" name="first" />
</kbase>
</kmodule>
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
StatelessKieSession kSession = kContainer.newStatelessKieSession("first");
for (int i = 0; i < 100; ++i) {
Bean bean = new Bean();
bean.setValue(i);
kSession.execute(bean);
}
查看源代码可知,其底层是对有状态的Session进行了包装,只是会做一次复制,这样当前线程就是这个Session唯一的执行线程了,可以保证同步调用。
0x03 动态规则加载
规则引擎最大的好处是规则和系统分离,这使得可以单独更新规则。6.x与maven耦合,可以直接通过maven坐标更新规则。
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.newKieContainer(
ks.newReleaseId("org.drools.test", "artifact-rule", "1.0") );
StatelessKieSession kSession = kContainer.newStatelessKieSession();
在更多的时候,我们希望自己进行规则的生成和加载,最直接的想法是给定一个规则字符串,直接更新。
(此时不需要kmodule.xml
和rule.drl
)
KieServices ks = KieServices.Factory.get();
KieFileSystem kfs = ks.newKieFileSystem();
kfs.write("src/main/resources/simple.drl", ruleString); // ruleString是规则字符串
KieBuilder kb = ks.newKieBuilder(kfs);
kb.buildAll();
// 以下几行代码可以看到载入错误
Results results = kb.getResults();
if (results.hasMessages(org.kie.api.builder.Message.Level.ERROR)) {
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
KieContainer kContainer = ks.newKieContainer(kb.getKieModule().getReleaseId());
StatelessKieSession kSession = kContainer.newStatelessKieSession();
Drools底层仍然是动态生成一个如同maven打包的jar,然后再将其载入。
Drools会将规则动态地编译成类以构建整个引擎,所以动态加载规则时候不可能避免地会占用一部分持久代,那么会OOM吗?
在我的机器上,我用以下JVM参数,启用了类卸载
-Xmx1024m
-XX:PermSize=64m
-XX:MaxPermSize=64m
-XX:+CMSClassUnloadingEnabled
规则是前面提到的rule.drl文件,一千条简单等于规则,结果:
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
说明是会OOM的。但是如果将持久代区域提高到128M,在我的机器上则不会发生OOM。
但动态加载后几乎每次都触发了FullGC,此时GC+加载时间会从几十秒到几百秒(是的,单位是秒)。将持久代大小提高后,可以减少触发FullGC的频率,但每次触发FullGC之后的加载还是耗时很久。(这里测试的规则较简单占用内存相对较少,真实的规则文件会导致不同的加载时间和内存占用)
0x04 定时器
Drools 6.x的一个新功能是直接支持在表达式中编写定时器,Drools规则支持的定时方法有很多,这里只演示基于cron表达式的规则。
规则文件rule.drl
,每秒打印一次时间
import java.util.Date;
rule "Timer"
timer (cron:0/1 * * * * ?)
when
eval(true)
then
System.out.println(new Date());
end
调用代码
KieServices ks = KieServices.Factory.get();
// 启用定时器规则
KieSessionConfiguration ksconf = ks.newKieSessionConfiguration();
ksconf.setOption( TimedRuleExectionOption.YES );
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("first", ksconf);
kSession.fireAllRules();
0x05 总结
Drools 6.x 并没有什么有价值的改进,6.x仍可以使用5.x的API。
另外这样一个规则稍微一多就吃内存的规则引擎,到底有多大意义?