Open TavenYin opened 2 years ago
本文主要想聊下这几个问题
7.69.0.Final
最简单的加载方式,官方的 demo 中使用的也是这种方式,从 classpath 下加载 kmodule 和规则资源。可以快速开始 Drools 应用开发
<dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>${drools.version}</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-traits</artifactId> <version>${drools.version}</version> </dependency>
<?xml version="1.0" encoding="UTF-8"?> <kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.drools.org/xsd/kmodule"> <kbase name="HelloWorldKB" packages="org.example.drools.helloworld"> <ksession name="HelloWorldKS"/> </kbase> </kmodule>
package org.example.drools.helloworld; rule "helloworld1" when then System.out.println("Hello World11111"); end rule "helloworld2" when then System.out.println("Hello World2"); end
KieServices ks = KieServices.Factory.get(); kieContainer = ks.newKieClasspathContainer(); KieSession kieSession = kieContainer.newKieSession("HelloWorldKS"); kieSession.fireAllRules(); kieSession.dispose();
当执行 ks.newKieClasspathContainer(); 时,会自动寻找 META-INF/kmodule.xml,用于创建 KieModule(KieModule 仅是对 KieBase 以及 KieSession 的定义)
ks.newKieClasspathContainer();
当执行 kieContainer.newKieSession("HelloWorldKS") 时,会先创建 KieBase,此时也会去编译规则(如果你的规则文件比较大的话,这个编译过程可能会很慢)。KieBase 创建完成后,使用 KieBase 创建 KieSession
kieContainer.newKieSession("HelloWorldKS")
使用该方式的优点是简单、可以快速开发,但是缺点也很明显,规则和配置文件绑定在项目中(耦合度太高)。如果你不需要修改规则文件,这种方式还是可以采纳的
KieServices ks = KieServices.Factory.get(); KieFileSystem kfs = ks.newKieFileSystem(); // kfs kfs.write("src/main/resources/KBase1/ruleSet1.drl", drl); kfs.write("src/main/resources/META-INF/kmodule.xml", ResourceFactory.newClassPathResource("META-INF/kmodule.xml")); kfs.write("pom.xml", ResourceFactory.newFileResource("your_path/pom.xml")); KieBuilder kieBuilder = ks.newKieBuilder(kfs); kieBuilder.buildAll(); // releaseId 与 pom 中声明的一致 // 如果 kfs 中未写入 pom 的话,使用 ks.getRepository().getDefaultReleaseId() KieContainer kieContainer = ks.newKieContainer(releaseId);
使用这种方式可以将规则和 kmodule.xml 存储在外部,简单说下流程
ks.getRepository().getDefaultReleaseId()
当你希望把 Drools 资源外部存储时,使用 KieBuilder 是不错的方案
Resource resource = ...; KieHelper helper = new KieHelper(); helper.addResource(resource, ResourceType.DRL); KieBase kBase = helper.build();
使用 KieHelper 可以帮你快速创建一个 KieBase,可以认为是 KieBuilder 的操作简化,内部还是使用了 KieFileSystem 和 KieBuilder,只不过在创建 KieContainer 之后新建了一个 KieBase 作为返回值
测试的时候,或者说想自己管理 KieBase 的话,可以使用这个 API,总的来说不推荐使用。
这是在 Drools 官方文档中看到的一个骚操作,通过动态加载 jar 的方式来实现资源加载和动态更新,下面简单介绍下。
首先我们需要将业务服务与 Drools 资源分离成两个 jar
Drools 资源 jar 具体结构如下,如果你习惯使用 drools-workbench 的话,也可以用它来创建资源 jar
│ pom.xml │ └───src ├───main │ ├───java │ └───resources │ ├───com │ │ └───company │ │ └───hello │ │ helloworld.drl │ │ │ └───META-INF │ kmodule.xml
pom 中需要注意的两点是
-SNAPSHOT
资源 jar 准备完成之后,使用命令 mvn clean deploy 将其推送到远端
mvn clean deploy
下面是业务工程的操作
首先 pom 中引入 kie-ci,这里注意啊,不要引入你刚刚创建的资源 jar
<dependency> <groupId>org.kie</groupId> <artifactId>kie-ci</artifactId> <version>7.69.0.Final</version> </dependency>
项目中加入如下代码
KieServices kieServices = KieServices.Factory.get(); // 注意这里的 releaseId 就是对应的是你资源 jar 的 groupId,artifactId,version ReleaseId releaseId = kieServices.newReleaseId( "org.company", "drl-base", "0.1.0-SNAPSHOT" ); KieContainer kContainer = kieServices.newKieContainer( releaseId ); KieScanner kScanner = kieServices.newKieScanner( kContainer ); kScanner.start( 10000L );
然后启动业务服务 jar
访问业务服务验证规则是否加载
更新资源 jar 并推送至远端 这时候可以看到业务进程会打出如下日志,说明规则更新成功
2022-05-19 16:43:11.223 INFO 20684 --- [ Timer-0] org.kie.api.builder.KieScanner : The following artifacts have been updated: {yourjarName}
验证规则更新
KieContainerImpl.updateToKieModule
看到第三步后,我在想我自己是否可以利用这个 updateToKieModule 方法来实现更新 Container 呢?后来尝试了一下,证明可以
updateToKieModule
这里就不贴代码了,大概就是下面这样
KieBuilder = ... KieModule kieModule = kieBuilder.getKieModule(); kContainer.updateToKieModule((InternalKieModule) kieModule);
上面讲到了 KieContainerImpl.updateToKieModule 的方式来更新规则库。
基于上面讲到的方式,其实可以想到。如果重新创建 KieContainer 的话,也相当于实现规则库动态更新。但是这种方式也存在一定问题
dispose
除此之外,KieBase 的实现类本身也提供了更新以及删除的 API
// 新增或者更新 KnowledgeBuilder kBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kBuilder.add(resource, ResourceType.DRL); if (kBuilder.hasErrors()) { KnowledgeBuilderErrors errors = kBuilder.getErrors(); log.error(errors.toString()); return; } InternalKnowledgeBase knowledgeBase = (InternalKnowledgeBase) kContainer.getKieBase(kieBaseName); knowledgeBase.addPackages(kBuilder.getKnowledgePackages()); // 删除规则 knowledgeBase.removeKiePackage(packageName); // 或者 knowledgeBase.removeRule(packageName, ruleName);
重点说明下,如果你要更新一个规则的话,直接调用 addPackages 即可,并不需要先删除再新增(这样反而有可能造成问题)
addPackages
这种方式相比上面说到的 KieContainerImpl.updateToKieModule 的方式颗粒度要小一些,updateToKieModule 会更新所有的 KBase
起因就是我想了解一下,KSession 正在执行时,更新 KBase 会有什么影响
举个例子具体说下
KnowledgeBuilder kbuilder = getKnowledgeBuilder("helloworld.drl"); InternalKnowledgeBase kieBase = KnowledgeBaseFactory.newKnowledgeBase(); kieBase.addPackages(kbuilder.getKnowledgePackages()); KieSession kieSession = kieBase.newKieSession(); kieSession.insert(1d); CompletableFuture.runAsync(() -> { kieBase.removeKiePackage("com.example.drools.helloworld"); log.info("remove package"); }).join(); kieSession.fireAllRules(); kieSession.dispose();
helloworld.drl 只有一个规则,在执行 fireAllRules 之前,执行了 KBase remove 操作,这会导致本次 fire 没有触发任何规则,因为此时 KBase 内部没有规则
这看起来好像挺合理的,但是如果你的本意是想先删除,再新增呢?删除 + 新增并没有一个原子操作,导致业务数据可能没有触发任何规则。
所以推荐尽可能不要在运行时做这种 删除 + 新增的操作
看到这时,其实我还有一个问题。当执行 kieSession.fireAllRules(); 时,规则库也允许被更新吗?
kieSession.fireAllRules();
由于篇幅问题,这里我直接说结论:
所以仅是动态更新规则的话,对 Drools 的执行是没有影响的
我觉得既然使用了规则引擎,解耦是非常重要的,所以比较推荐使用 KieBuilder 的方式来加载规则;如果你真的就不需要规则资源外部存储的话,直接使用 ks.newKieClasspathContainer(); 就可以了
如果你想使用 KieScanner 的话,一定要注意做好快照版本的管理。生产环境和开发环境不能使用同一个 maven 仓库,或者使用不同的版本防止开发环境更新影响生产环境
规则库动态更新方案的话,本文总结了三种
如果你需要动态更新 KieModule 的话,可以考虑使用 updateToKieModule 或者重新创建 KieContainer 的方式(需要注意销毁旧的 Container)
如果你仅仅是需要动态更新规则的话,可以考虑使用 InternalKnowledgeBase(该方式开销更小,需要注意不要使用删除+新增的方式)和 updateToKieModule (开销相对前者较大)
评论测试
前言
本文主要想聊下这几个问题
版本
7.69.0.Final
规则的加载
1. 使用 KieClasspathContainer
最简单的加载方式,官方的 demo 中使用的也是这种方式,从 classpath 下加载 kmodule 和规则资源。可以快速开始 Drools 应用开发
1.1. 引入 Drools 依赖
1.2. 新建 resource/META-INF/kmodule.xml
1.3. 新建 resource/org/example/drools/helloworld/hello.drl
1.4. 创建 ClasspathContainer,并触发规则
创建 ClasspathContainer 流程浅析
当执行
ks.newKieClasspathContainer();
时,会自动寻找 META-INF/kmodule.xml,用于创建 KieModule(KieModule 仅是对 KieBase 以及 KieSession 的定义)当执行
kieContainer.newKieSession("HelloWorldKS")
时,会先创建 KieBase,此时也会去编译规则(如果你的规则文件比较大的话,这个编译过程可能会很慢)。KieBase 创建完成后,使用 KieBase 创建 KieSessionClasspathContainer 方式小结
使用该方式的优点是简单、可以快速开发,但是缺点也很明显,规则和配置文件绑定在项目中(耦合度太高)。如果你不需要修改规则文件,这种方式还是可以采纳的
2. KieBuilder
使用这种方式可以将规则和 kmodule.xml 存储在外部,简单说下流程
ks.getRepository().getDefaultReleaseId()
作为参数传入当你希望把 Drools 资源外部存储时,使用 KieBuilder 是不错的方案
3. KieHelper
使用 KieHelper 可以帮你快速创建一个 KieBase,可以认为是 KieBuilder 的操作简化,内部还是使用了 KieFileSystem 和 KieBuilder,只不过在创建 KieContainer 之后新建了一个 KieBase 作为返回值
测试的时候,或者说想自己管理 KieBase 的话,可以使用这个 API,总的来说不推荐使用。
4. KieScanner
这是在 Drools 官方文档中看到的一个骚操作,通过动态加载 jar 的方式来实现资源加载和动态更新,下面简单介绍下。
首先我们需要将业务服务与 Drools 资源分离成两个 jar
Drools 资源 jar 具体结构如下,如果你习惯使用 drools-workbench 的话,也可以用它来创建资源 jar
pom 中需要注意的两点是
-SNAPSHOT
结尾资源 jar 准备完成之后,使用命令
mvn clean deploy
将其推送到远端下面是业务工程的操作
首先 pom 中引入 kie-ci,这里注意啊,不要引入你刚刚创建的资源 jar
项目中加入如下代码
然后启动业务服务 jar
访问业务服务验证规则是否加载
更新资源 jar 并推送至远端 这时候可以看到业务进程会打出如下日志,说明规则更新成功
验证规则更新
KieScanner 原理浅析
KieContainerImpl.updateToKieModule
来更新容器,本质上是更新 KBase看到第三步后,我在想我自己是否可以利用这个
updateToKieModule
方法来实现更新 Container 呢?后来尝试了一下,证明可以这里就不贴代码了,大概就是下面这样
规则库更新
1. updateToKieModule
上面讲到了
KieContainerImpl.updateToKieModule
的方式来更新规则库。2. 创建新的 KieContainer
基于上面讲到的方式,其实可以想到。如果重新创建 KieContainer 的话,也相当于实现规则库动态更新。但是这种方式也存在一定问题
dispose
方法清理资源可能会销毁正在使用的 kSession。3. InternalKnowledgeBase
除此之外,KieBase 的实现类本身也提供了更新以及删除的 API
这种方式相比上面说到的
KieContainerImpl.updateToKieModule
的方式颗粒度要小一些,updateToKieModule
会更新所有的 KBase并发更新规则
起因就是我想了解一下,KSession 正在执行时,更新 KBase 会有什么影响
举个例子具体说下
helloworld.drl 只有一个规则,在执行 fireAllRules 之前,执行了 KBase remove 操作,这会导致本次 fire 没有触发任何规则,因为此时 KBase 内部没有规则
这看起来好像挺合理的,但是如果你的本意是想先删除,再新增呢?删除 + 新增并没有一个原子操作,导致业务数据可能没有触发任何规则。
所以推荐尽可能不要在运行时做这种 删除 + 新增的操作
看到这时,其实我还有一个问题。当执行
kieSession.fireAllRules();
时,规则库也允许被更新吗?由于篇幅问题,这里我直接说结论:
所以仅是动态更新规则的话,对 Drools 的执行是没有影响的
全文总结
我觉得既然使用了规则引擎,解耦是非常重要的,所以比较推荐使用 KieBuilder 的方式来加载规则;如果你真的就不需要规则资源外部存储的话,直接使用
ks.newKieClasspathContainer();
就可以了如果你想使用 KieScanner 的话,一定要注意做好快照版本的管理。生产环境和开发环境不能使用同一个 maven 仓库,或者使用不同的版本防止开发环境更新影响生产环境
规则库动态更新方案的话,本文总结了三种
KieContainerImpl.updateToKieModule
如果你需要动态更新 KieModule 的话,可以考虑使用 updateToKieModule 或者重新创建 KieContainer 的方式(需要注意销毁旧的 Container)
如果你仅仅是需要动态更新规则的话,可以考虑使用 InternalKnowledgeBase(该方式开销更小,需要注意不要使用删除+新增的方式)和 updateToKieModule (开销相对前者较大)