Geek漫游指南

Drools规则引擎

date
slug
drools
author
status
Public
tags
技术分享
summary
type
Post
thumbnail
category
💻 Backend
updatedAt
Oct 29, 2023 07:21 AM
简单入门
from entry-point是Drools规则引擎中的语法,用于从指定的入口点中匹配对象。入口点是指在规则引擎中定义的逻辑通道,用于将事实对象流入规则引擎中。通过使用from entry-point语法,可以在规则中指定从特定入口点中匹配对象。
 
在Drools中,可以通过使用entry-point关键字来定义入口点,然后在规则中使用from entry-point来指定要从哪个入口点中匹配对象。
 
// 定义入口点 EntryPoint entryPoint = kieSession.getEntryPoint("Fire"); // 向入口点中插入对象 entryPoint.insert(new Fire()); // 规则中使用 from entry-point 匹配对象 rule "Example Rule" when $fire: Fire() from entry-point "Fire" then // 规则的条件满足时执行的动作 System.out.println("Fire detected!"); end
 

KIE Api

#KieServices

该接口提供了很多方法,可以通过这些方法访问KIE关于构建和运行的相关对象,比如说可以获取KieContainer,利用KieContainer来访问KBase和KSession等信息;可以获取KieRepository对象,利用KieRepository来管理KieModule等。 KieServices就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作。
// 通过单例创建KieServices KieServices kieServices = KieServices.Factory.get(); // 获取KieContainer KieContainer kieContainer = kieServices.getKieClasspathContainer(); // 获取KieRepository KieRepository kieRepository = kieServices.getRepository();
 

#KieContainer

可以理解KieContainer就是一个KieBase的容器。提供了获取KieBase的方法和创建KieSession的方法。其中获取KieSession的方法内部依旧通过KieBase来创建KieSession。
// 通过单例创建KieServices KieServices kieServices = KieServices.Factory.get(); // 获取KieContainer KieContainer kieContainer = kieServices.getKieClasspathContainer(); // 获取KieBase KieBase kieBase = kieContainer.getKieBase(); // 创建KieSession KieSession kieSession = kieContainer.newKieSession("session-name");
 

#KieBase

KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession。
// 获取KieBase KieBase kieBase = kieContainer.getKieBase(); KieSession kieSession = kieBase.newKieSession(); StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();
 

#KieSession

KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实Fact”,并对运行时数据实时进行规则运算。通过KieContainer创建KieSession是一种较为方便的做法,其本质上是从KieBase中创建出来的。KieSession就是应用程序跟规则引擎进行交互的会话通道。 创建KieBase是一个成本非常高的事情,KieBase会建立知识(规则、流程)仓库,而创建KieSession则是一个成本非常低的事情,所以KieBase会建立缓存,而KieSession则不必。

#KieRepository

KieRepository是一个单例对象,它是存放KieModule的仓库,KieModule由kmodule.xml文件定义(当然不仅仅只是用它来定义)。
// 通过单例创建KieServices KieServices kieServices = KieServices.Factory.get(); // 获取KieRepository KieRepository kieRepository = kieServices.getRepository();
 

#KieProject

KieContainer通过KieProject来初始化、构造KieModule,并将KieModule存放到KieRepository中,然后KieContainer可以通过KieProject来查找KieModule定义的信息,并根据这些信息构造KieBase和KieSession。

#ClasspathKieProject

ClasspathKieProject实现了KieProject接口,它提供了根据类路径中的META-INF/kmodule.xml文件构造KieModule的能力,是基于Maven构造Drools组件的基本保障之一。意味着只要按照前面提到过的Maven工程结构组织我们的规则文件或流程文件,只用很少的代码完成模型的加载和构建。

#kmodule.xml

#kbase的属性:

属性名
默认值
合法的值
描述
name
none
any
KieBase的名称,这个属性是强制的,必须设置。
includes
none
逗号分隔的KieBase名称列表
意味着本KieBase将会包含所有include的KieBase的rule、process定义制品文件。非强制属性。
packages
all
逗号分隔的字符串列表
默认情况下将包含resources目录下面(子目录)的所有规则文件。也可以指定具体目录下面的规则文件,通过逗号可以包含多个目录下的文件。
default
false
true, false
表示当前KieBase是不是默认的,如果是默认的话,不用名称就可以查找到该KieBase,但是每一个module最多只能有一个KieBase。
equalsBehavior
identity
identity,equality
顾名思义就是定义“equals”(等于)的行为,这个equals是针对Fact(事实)的,当插入一个Fact到Working Memory中的时候,Drools引擎会检查该Fact是否已经存在,如果存在的话就使用已有的FactHandle,否则就创建新的。而判断Fact是否存在的依据通过该属性定义的方式来进行的:设置成 identity,就是判断对象是否存在,可以理解为用==判断,看是否是同一个对象;
eventProcessingMode
cloud
cloud, stream
当以云模式编译时,KieBase将事件视为正常事实,而在流模式下允许对其进行时间推理。
declarativeAgenda
disabled
disabled,enabled
这是一个高级功能开关,打开后规则将可以控制一些规则的执行与否。

#ksession的属性:

属性名
默认值
合法的值
描述
name
none
any
KieSession的名称,该值必须唯一,也是强制的,必须设置。
type
stateful
stateful, stateless
定义该session到底是有状态(stateful)的还是无状态(stateless)的,有状态的session可以利用Working Memory执行多次,而无状态的则只能执行一次。
default
false
true, false
定义该session是否是默认的,如果是默认的话则可以不用通过session的name来创建session,在同一个module中最多只能有一个默认的session。
clockType
realtime
realtime,pseudo
定义时钟类型,用在事件处理上面,在复合事件处理上会用到,其中realtime表示用的是系统时钟,而pseudo则是用在单元测试时模拟用的。
beliefSystem
simple
simple,defeasible, jtms
定义KieSession使用的belief System的类型。
 
 
// 在类路径下加载所有的规则文件 KieServices kieServices = KieServices.Factory.get(); KieContainer kContainer = kieServices.getKieClasspathContainer();
 

Drools思维导图

 
当业务用户或自动化系统在Drools中添加或更新与规则相关的信息时,这些信息以一个或多个fact的形式插入到Drools规则引擎的工作内存中。Drools规则引擎将这些fact与存储在生产内存中的规则条件进行匹配,以确定符合条件的规则执行。 (将fact与rule进行匹配的这个过程通常称为模式匹配。)当规则条件满足时,Drools规则引擎会激活并在议程中注册规则,然后对优先级排序或冲突的规则进行准备以便执行。
 
下图说明了 Drools 规则引擎的这些基本组件:
notion image
 
 
 
在Drools中,KIE会话用于存储和执行运行时数据。KIE会话可以从KIE基础中创建,也可以直接从KIE容器中创建,如果您在项目的KIE模块描述符文件(kmodule.xml)中定义了KIE会话
Example KIE session configuration in a kmodule.xml file
<kmodule> ... <kbase> ... <ksession name="KSession2_1" type="stateless" default="true" clockType="realtime"> ... </kbase> ... </kmodule>
 
KIE 基础是您在项目的 KIE 模块描述符文件 (kmodule.xml) 中定义的存储库,包含 Drools 中的所有内容,但不包含任何运行时数据。
Example KIE base configuration in a kmodule.xml file
<kmodule> ... <kbase name="KBase2" default="false" eventProcessingMode="stream" equalsBehavior="equality" declarativeAgenda="enabled" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1"> ... </kbase> ... </kmodule>
 
KIE 会话可以是无状态的或有状态的。 在无状态 KIE 会话中,先前调用 KIE 会话的数据(先前的会话状态)将在会话调用之间被丢弃。 在有状态的 KIE 会话中,该数据将被保留。 您使用的 KIE 会话类型取决于您的项目要求以及您希望如何保存来自不同资产调用的数据。
 

无状态KIE Session

无状态的KIE会话是指每次调用时都不会保留之前的数据状态,它仅仅基于当前传入的数据和KIE基础的内容进行执行。这意味着每次调用无状态的KIE会话时,它都是一个全新的会话,不会记住之前的执行结果或数据。它的行为类似于一个函数,根据输入的数据和固定的规则进行计算,而不依赖于之前的状态。因此,无状态的KIE会话适用于不需要保留历史状态或与之前的数据无关的场景
 
StatelessKieSession kSession = kContainer.newStatelessKieSession(); Applicant applicant = new Applicant("Mr John Smith", 16); assertTrue(applicant.isValid()); ksession.execute(applicant); assertFalse(applicant.isValid());
在无状态 KIE 会话配置中,execute() 调用充当实例化 KieSession 对象、添加所有用户数据并执行用户命令、调用 fireAllRules(),然后调用 dispose() 的组合方法。 因此,对于无状态 KIE 会话,您不需要像使用有状态 KIE 会话那样在会话调用后调用 fireAllRules() 或调用 dispose()。
 
 

无状态KIE会话中的全局变量

kieSession.setGlobal("orderDiscount", orderDiscount); kieSession.execute(orderRequest); System.out.println(kieSession.getGlobals().get("orderDiscount")); // Set up a list of commands. List cmds = new ArrayList(); cmds.add(CommandFactory.newSetGlobal("list1", new ArrayList(), true)); cmds.add(CommandFactory.newInsert(new Person("jon", 102), "person")); cmds.add(CommandFactory.newQuery("Get People" "getPeople")); // Execute the list. ExecutionResults results = ksession.execute(CommandFactory.newBatchExecution(cmds)); // Retrieve the `ArrayList`. results.getValue("list1"); // Retrieve the inserted `Person` fact. results.getValue("person"); // Retrieve the query as a `QueryResults` instance. results.getValue("Get People");
 
 
规则引擎:通过匹配规则库和事实库,挑选出对应的规则到日程,最后执行对应的action。 rate算法简介 Rete匹配算法是一种进行大量模式集合和大量对象集合间比较的高效方法,通过这种方法找出所有匹配各个模式的对象。 Rete算法以牺牲内存换取高速的策略 Rete算法分为两个部分:规则编译(rule compilation)、运行时执行(runtime execution).
 
notion image
 
 

有状态KIE sessions

有状态 KIE 会话是使用推理随着时间的推移对事实进行迭代更改的会话。 在有状态 KIE 会话中,先前调用 KIE 会话的数据(先前的会话状态)在会话调用之间保留,而在无状态 KIE 会话中,该数据将被丢弃。
 
import org.drools.core.DroolsExecutor; import org.drools.core.model.Fire; import org.drools.core.model.Room; import org.drools.core.model.Sprinkler; import org.junit.Test; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.FactHandle; import java.util.HashMap; import java.util.Map; /** * @author : kenny * @since : 2023/6/21 **/ public class DroolsStatefulExecutorTest { @Test public void test(){ System.err.println("Before factory init...."); KieServices kieServices = KieServices.Factory.get(); System.err.println("After factory init...."); KieContainer kContainer = kieServices.getKieClasspathContainer(); KieSession ksession = kContainer.newKieSession(); String[] names = new String[]{"kitchen", "bedroom", "office", "livingroom"}; Map<String,Room> name2room = new HashMap<>(); for( String name: names ){ Room room = new Room( name ); name2room.put( name, room ); ksession.insert( room ); Sprinkler sprinkler = new Sprinkler( room ); ksession.insert( sprinkler ); } ksession.fireAllRules(); Fire kitchenFire = new Fire( name2room.get( "kitchen" ) ); Fire officeFire = new Fire( name2room.get( "office" ) ); FactHandle kitchenFireHandle = ksession.insert( kitchenFire ); FactHandle officeFireHandle = ksession.insert( officeFire ); ksession.fireAllRules(); ksession.delete( kitchenFireHandle ); ksession.delete( officeFireHandle ); ksession.fireAllRules(); ksession.dispose(); // 在完成规则执行后,记得释放会话资源 } } public class Room { private String name; // Getter and setter methods } public class Sprinkler { private Room room; private boolean on; // Getter and setter methods } public class Fire { private Room room; // Getter and setter methods } public class Alarm { } import org.drools.core.model.Fire import org.drools.core.model.Sprinkler import org.drools.core.model.Alarm rule "When there is a fire turn on the sprinkler" when Fire($room : room) $sprinkler : Sprinkler(room == $room, on == false) then modify($sprinkler) { setOn(true) }; // 有状态 KIE 会话则依赖modify规则中的语句来通知 Drools 规则引擎更改 System.out.println("Turn on the sprinkler for room "+$room.getName()); end rule "Raise the alarm when we have one or more fires" when exists Fire() then insert( new Alarm() ); System.out.println( "Raise the alarm" ); end rule "Cancel the alarm when all the fires have gone" when not Fire() $alarm : Alarm() then delete( $alarm ); System.out.println( "Cancel the alarm" ); end rule "Status output when things are ok" when not Alarm() not Sprinkler( on == true ) then System.out.println( "Everything is ok" ); end
 
KIE session pools
In use cases with large amounts of KIE runtime data and high system activity, KIE sessions might be created and disposed very frequently. A high turnover of KIE sessions is not always time consuming, but when the turnover is repeated millions of times, the process can become a bottleneck and require substantial clean-up effort.
For these high-volume cases, you can use KIE session pools instead of many individual KIE sessions. To use a KIE session pool, you obtain a KIE session pool from a KIE container, define the initial number of KIE sessions in the pool, and create the KIE sessions from that pool as usual:
Example KIE session pool
// Obtain a KIE session pool from the KIE container KieContainerSessionsPool pool = kContainer.newKieSessionsPool(10); // Create KIE sessions from the KIE session pool KieSession kSession = pool.newKieSession();
 
In this example, the KIE session pool starts with 10 KIE sessions in it, but you can specify the number of KIE sessions that you need. This integer value is the number of KIE sessions that are only initially created in the pool. If required by the running application, the number of KIE sessions in the pool can dynamically grow beyond that value.
After you define a KIE session pool, the next time you use the KIE session as usual and call dispose() on it, the KIE session is reset and pushed back into the pool instead of being destroyed.
KIE session pools typically apply to stateful KIE sessions, but KIE session pools can also affect stateless KIE sessions that you reuse with multiple execute() calls. When you create a stateless KIE session directly from a KIE container, the KIE session continues to internally create a new KIE session for each execute() invocation. Conversely, when you create a stateless KIE session from a KIE session pool, the KIE session internally uses only the specific KIE sessions provided by the pool.
When you finish using a KIE session pool, you can call the shutdown() method on it to avoid memory leaks. Alternatively, you can call dispose() on the KIE container to shut down all the pools created from the KIE container.
 
 
 
 
 
// 从drools中获取new出来的对象 List<ChildBusPass> childBusPassList = new ArrayList<>(); for (Object object : ksession.getObjects()) { if (object instanceof ChildBusPass) { childBusPassList.add((ChildBusPass) object); } }
 
 
 
 
Drools 规则引擎支持以下事实相等模式,这些模式确定 Drools 规则引擎如何存储和比较插入的事实:
  • identity:(默认)Drools 规则引擎使用 来IdentityHashMap存储所有插入的事实。对于每个新事实插入,Drools 规则引擎都会返回一个新FactHandle对象。如果再次插入事实,Drools 规则引擎将返回原始FactHandle对象,忽略同一事实的重复插入。在此模式下,只有当两个事实是具有相同身份的完全相同的对象时,对于 Drools 规则引擎来说它们才是相同的。
  • equality:Drools规则引擎使用aHashMap来存储所有插入的事实。FactHandle仅当插入事实不等于现有事实时,Drools 规则引擎才会根据equals()插入事实的方法返回新对象。在此模式下,如果两个事实以相同的方式组成,则对于 Drools 规则引擎而言,这两个事实是相同的,无论身份如何。当您希望基于特征相等而不是显式标识来评估对象时,请使用此模式。
 
 
 
Drools 规则引擎中的执行控制
当新的规则数据进入 Drools 规则引擎的工作内存时,规则可能会完全匹配并有资格执行。单个工作内存操作可能会导致多个符合条件的规则执行。当规则完全匹配时,Drools 规则引擎会创建一个 internalMatch 实例,引用匹配的规则和事实,并将 internalMatch 添加到 Drools 规则引擎的议程(agenda)中。议程使用冲突解决策略控制这些规则 internalMatch 的执行顺序。
Here's the translation:
当新的规则数据进入 Drools 规则引擎的工作内存时,规则可能会完全匹配并有资格执行。单个工作内存操作可能会导致多个符合条件的规则执行。当规则完全匹配时,Drools 规则引擎会创建一个 internalMatch 实例,引用匹配的规则和事实,并将 internalMatch 添加到 Drools 规则引擎的议程(agenda)中。议程使用冲突解决策略控制这些规则 internalMatch 的执行顺序。
 
在 Java 应用程序中首次调用 fireAllRules() 之后,Drools 规则引擎会循环执行两个阶段:
议程评估阶段(Agenda Evaluation):在这个阶段,Drools 规则引擎选择可以执行的所有规则。如果不存在可执行的规则,则执行循环结束。如果找到可执行的规则,Drools 规则引擎会在议程中注册 internalMatch,然后进入工作内存操作阶段以执行规则的结果动作。
工作内存操作阶段(Working Memory Actions):在这个阶段,Drools 规则引擎为议程中先前注册的所有激活规则执行规则的结果动作(即每个规则的 then 部分)。在完成所有结果动作或主 Java 应用程序进程再次调用 fireAllRules() 后,Drools 规则引擎返回到议程评估阶段重新评估规则
notion image
当议程上存在多个规则时,执行一个规则可能会导致另一个规则从议程中移除。为了避免这种情况,你可以在 Drools 规则引擎中定义规则的执行方式和顺序。一些常见的定义规则执行顺序的方法包括使用规则优先级(salience)、议程分组(agenda groups)或 internalMatch 分组。
以下是对这些方法的解释:
  1. 规则优先级(Rule Salience):
      • 规则优先级通过为规则设置一个数值来定义其执行顺序。数值越高的规则优先级越高,即会先执行。
      • 通过设置规则的 salience 属性来指定规则的优先级。例如,salience 10 表示比 salience 5 的规则具有更高的优先级。
  1. 议程分组(Agenda Groups):
      • 议程分组可以将规则分成不同的组,每个组具有独立的执行顺序。默认情况下,所有规则属于默认分组。
      • 通过将规则的 agenda-group 属性设置为特定分组名称,可以将规则分配给该分组。
      • 在执行规则时,你可以选择只执行特定分组的规则,从而控制规则的执行顺序。
  1. internalMatch 分组:
      • internalMatch 是规则匹配的实例,可以通过对规则设置 activation-group 属性来将其分组。
      • 当一个规则的 internalMatch 被激活时,只有同一组中的其他规则中的 internalMatch 才会被激活。
      • 这样可以确保同一组中的规则按照特定顺序执行,而不会被其他组中的规则干扰。
通过使用上述方法之一或它们的组合,你可以明确规则的执行顺序和方式,以满足特定的业务需求。根据你的具体情况,选择适合的方法来控制规则引擎的执行行为。
 
 
使用案例
package com.drools.core.model; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author : kenny * @since : 2023/7/3 **/ public class Order { private int orderId; private double amount; private double discount; public Order(int orderId, double amount, double discount) { this.orderId = orderId; this.amount = amount; this.discount = discount; } public int getOrderId() { return orderId; } public void setOrderId(int orderId) { this.orderId = orderId; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } public double getDiscount() { return discount; } public void setDiscount(double discount) { this.discount = discount; } public static void main(String[] args) { KieServices kieServices = KieServices.get(); KieContainer kieContainer = kieServices.getKieClasspathContainer(); // 创建KieSession KieSession kieSession = kieContainer.newKieSession(); // 创建订单对象 Order order = new Order(1, 1200, 100); // 将订单对象插入到KieSession中 kieSession.insert(order); // 设置当前的AgendaGroup为"ValidationGroup" kieSession.getAgenda().getAgendaGroup("ValidationGroup").setFocus(); // 执行规则 kieSession.fireAllRules(); // 设置当前的AgendaGroup为"DiscountGroup" kieSession.getAgenda().getAgendaGroup("DiscountGroup").setFocus(); // 执行规则 kieSession.fireAllRules(); // 关闭KieSession kieSession.dispose(); } } import com.drools.core.model.Order; agenda-group "ValidationGroup" rule "CheckAmountRule" when $order: Order(amount > 1000) then System.out.println("Order amount exceeds the limit: " + $order.getAmount()); end agenda-group "DiscountGroup" rule "CalculateDiscountRule" when $order: Order() then $order.setDiscount($order.getAmount() * 0.1); System.out.println("Discount calculated: " + $order.getDiscount()); end
 
 
在Drools规则引擎中,fireAllRules()和fireUntilHalt()是两种不同的规则执行方法。
  1. fireAllRules()方法:
      • fireAllRules()方法是规则引擎的一次性执行方法。
      • 当调用fireAllRules()方法时,规则引擎会按照当前规则流程(Agenda)中的规则顺序,依次执行匹配的规则。
      • 该方法会一直执行,直到所有匹配的规则都被执行完毕或没有可执行的规则。
      • 在执行期间,规则引擎会根据规则条件的匹配情况和规则的操作,对事实(Facts)进行处理,可能会产生新的事实并触发其他规则的执行。
      • fireAllRules()方法通常用于在规则引擎中执行一次规则匹配和执行的操作。
  1. fireUntilHalt()方法:
      • fireUntilHalt()方法是规则引擎的持续执行方法。
      • 当调用fireUntilHalt()方法时,规则引擎会按照当前规则流程(Agenda)中的规则顺序,依次执行匹配的规则。
      • 该方法会一直执行,直到手动调用halt()方法停止执行或中断程序。
      • 在执行期间,规则引擎会不断地进行规则匹配和执行,响应新的事实并触发其他规则的执行。
      • fireUntilHalt()方法通常用于需要持续监听和处理规则引擎的情况,例如实时数据流处理、事件驱动的系统等。
总结:
  • fireAllRules()方法用于一次性执行规则引擎中的规则,执行完毕后结束。
  • fireUntilHalt()方法用于持续执行规则引擎中的规则,需要手动调用halt()方法停止执行。
根据具体的需求,您可以选择使用适合的方法来控制规则的执行方式。
 
 
 
Fact propagation modes(事实传播模式)是Drools规则引擎中的一个概念,用于定义事实(Facts)在规则执行期间如何进行传播和处理。它控制着规则引擎在执行规则时如何处理新的事实,并决定了规则中的条件是否需要重新匹配。
Drools中有三种常见的事实传播模式:
  1. Immediate Mode(立即模式):
      • 在立即模式下,当规则引擎插入一个新的事实后,会立即对新事实进行匹配和执行。
      • 插入的事实将立即参与规则的匹配,可能触发其他规则的执行,可能导致新的事实产生。
      • 立即模式适用于需要即时响应和处理新事实的场景。
  1. Eager Mode(急切模式):
      • 在急切模式下,当规则引擎插入一个新的事实后,会暂停当前规则的执行,重新匹配所有规则并执行匹配的规则。
      • 插入的事实会导致规则引擎重新执行规则流程,重新匹配所有的规则条件,可能触发其他规则的执行,可能导致新的事实产生。
      • 急切模式适用于需要重新评估所有规则并执行全局计算的场景。
  1. Semi-Eager Mode(半急切模式):
      • 半急切模式是立即模式和急切模式的折中方案。
      • 在半急切模式下,当规则引擎插入一个新的事实后,会先完成当前规则的执行,然后重新匹配其他规则并执行匹配的规则。
      • 插入的事实会等待当前规则的执行完成,然后才会重新匹配其他规则条件,并执行匹配的规则。
      • 半急切模式适用于需要在规则执行过程中处理新事实,但又希望先完成当前规则执行的场景。
通过选择适当的事实传播模式,可以根据业务需求和规则的逻辑来控制规则引擎的行为。不同的模式可以对性能、规则执行顺序和新事实处理方式产生影响,因此在使用规则引擎时需要根据具体情况进行选择和配置。