探究drools规则引擎匹配的过程
背景
drools规则引擎实现了将业务决策从应用程序代码中分离出来,接收数据输入,解释业务规则,并根据业务规则做出业务决策。其实规则引擎其实就是一个输入输出平台。在使用过程中发现框架的bug,在此分享一下踩坑、脱坑的经验。
问题描述和复现:
有4个规则:a_1、b_1、b_2、b_3
当规则a_1更新 dt2 的属性t="YES"后,后边的几个规则判断 dt2的属性 t == “YES”,却始终匹配不上,也就是后边规则的输入依赖前边的规则的输出,依赖对象值不会更新。
规则文件如下
package com.ai.prd
import com.itors.kie.dto.Dt2
import com.itors.kie.dto.Dt1rule "a_1"salience 100enabled trueruleflow-group "group_a"lock-on-active truedialect "mvel"whendt1 : Dt1(a == 1)dt2 : Dt2( )thenSystem.out.println("a_1 has been MatchFired!");modify( dt2 ) {setT("YES")}
endrule "b_1"salience 99enabled trueruleflow-group "group_b"lock-on-active truedialect "mvel"whendt1 : Dt1( )dt2 : Dt2( )eval(dt2.getT()=="YES")thenSystem.out.println("b_1 has been MatchFired!");
endrule "b_2"salience 98enabled trueruleflow-group "group_b"lock-on-active truedialect "mvel"whendt1 : Dt1( )dt2 : Dt2( t:t )eval(t=="YES")thenSystem.out.println("b_2 has been MatchFired!");
endrule "b_3"salience 97enabled trueruleflow-group "group_b"lock-on-active truedialect "mvel"whendt2 : Dt2( )eval(dt2.getT()=="YES")thenSystem.out.println("b_3 has been MatchFired!");
end
两个java对象如下:
public class Dt1 implements java.io.Serializable{private java.lang.Integer a;private java.lang.Integer b;//setter getter
}public class Dt2 implements java.io.Serializable{private java.lang.String t;//setter getter
}
项目的代码是:
Dt1 dt1 = new Dt1();
dt1.setA(1);
Dt2 dt2 = new Dt2();
kSession.insert(dt1);
kSession.insert(dt2);
kSession.getAgenda().getAgendaGroup("group_b").setFocus();
kSession.getAgenda().getAgendaGroup("group_a").setFocus();
kSession.fireAllRules();
执行控制台输出:
a_1 has been MatchFired!
b_2 has been MatchFired!
b_3 has been MatchFired!
从 kSession.fireAllRules() 开始往下查,最终会到drools的核心方法 DefaultAgenda.fireLoop
private int fireLoop(AgendaFilter agendaFilter, int fireLimit, RestHandler restHandler, boolean isInternalFire) {int fireCount = 0;try {// 根节点,会把我们插入的第一个fact对象dt1封装为PropagationEntry的链表结构,head的next指针会指向下一个PropagationEntry,即dt2PropagationEntry head = propagationList.takeAll();int returnedFireCount;boolean limitReached = fireLimit == 0; // 第一层循环,状态即是否在运转while ( isFiring() ) {// 循环 propagationList,拿着里边的Fact依次去构建网络if ( head != null ) {propagationList.flush(head);head = null;}// 这里又判断了一下状态机器的状态,因为在flush的时候,可能会造成状态机的异常if (!isFiring()) {break;}// 这个方法是重点,下边会重点讲到evaluateEagerList();// 获取下一个获取焦点的议程组InternalAgendaGroup group = getNextFocus();// 如果获取到议程组,并且执行次数没有达到限制if ( group != null && !limitReached ) {// 执行规则,并返回触发规则的数量returnedFireCount = ruleEvaluator.evaluateAndFire( agendaFilter, fireCount, fireLimit, group );fireCount += returnedFireCount;limitReached = ( fireLimit > 0 && fireCount >= fireLimit );// 重置链表head = propagationList.takeAll();} else {returnedFireCount = 0; // no rules fired this iteration, so we know this is 0group = null; // set the group to null in case the fire limit has been reached}// 当本次循环执行的规则总数为0,且链表的头指针为空,且。。。。if ( returnedFireCount == 0 && head == null && ( group == null || ( group.isEmpty() && !group.isAutoDeactivate() ) ) && !flushExpirations() ) {head = restHandler.handleRest( this, isInternalFire );if (!isInternalFire && head == null) {// 这是循环的出口,break;}}}if ( this.focusStack.size() == 1 && this.mainAgendaGroup.isEmpty() ) {// the root MAIN agenda group is empty, reset active to false, so it can receive more activations.this.mainAgendaGroup.setActive( false );}} finally {// makes sure the engine is inactive, if an exception is thrown.// if it safely returns, then the engine should already be inactiveif (isInternalFire) {executionStateMachine.immediateHalt(propagationList);}}return fireCount;
}
public void evaluateEagerList() {// eager 是一个LinkedList数据结构,里边放的是本次循环需要执行的规则列表,有意思的是// ,在规则执行的过程中,会触发eager添加元素,重新的去渲染内存网络,这也是drools规则// 引擎会陷入死循环的原因,while ( !eager.isEmpty() ) {RuleAgendaItem item = eager.removeFirst();if (item.isRuleInUse()) { // this rule could have been removed by an incremental compilationevaluateQueriesForRule( item );RuleExecutor ruleExecutor = item.getRuleExecutor();ruleExecutor.evaluateNetwork( this );}}
}
evaluateEagerList方法是将需要渲染的规则循环,依次的去渲染内存网络。但是debug发现的规则执行顺序是:
a_1→ b_1→ b_2→ b_3→ b_2→ b_3
b_2 和 b_3的第二次执行是因为a_1方法将dt2更新导致b_2 和 b_3 重新放入了队列的吗?那么为什么b_1没放进去?带着疑问继续看,既然没把b_1添加到eager里,那就从eager的添加元素的方法debug
eager.add() 是在 org.drools.core.common.DefaultAgenda#addEagerRuleAgendaItem中使用,继续在这个方法里debug看堆栈信息


进到BetaNode方法中查看,多次debug发现,b_1 和 b_2的区别就是==getRightInferredMask() ==,这个方法返回的是,当前beta节点中属性的推断掩码,b_1中并没有任何推断,所以b_1 返回的是0,b_2中有推断,返回是的4,这也是b_1 和b_2的区别。至于为什么b_2为什么4,又跟了下代码,它是在渲染的时候,会根据推断条件生成id,这个id是自增的。有兴趣的可自行跟一下源码。

但是b_3有些不同,b_3的堆栈信息如下,b_3因为LHS只有一个条件,所以它不是Beta节点,而是LeftInputAdaptNode


从上图可以看出,其实在LeftInputAdaptNode中也有判断,但这个条件是横通过的

根据以上debug,补一下引擎在内存中生成的规则节点网络图

总结:
1、当规则中有触发modify Fact时,只会触发LHS中有这个Fact的规则,且存在Fact的逻辑推断,否则不会重新触发
2、规则文件中,对于LHS只有一个对象的规则,内存里只有LeftInputAdaptNode,他的sink就是OBN
3、JoinNode 其实是继承BetaNode的,它属于Beta的一种
4、drools引擎其实对于eval函数是无感的
5、关于beta节点,不仅有left 和 right指针,还有pre next指针,在属性更新的时候,直接循环beta节点进行节点的按需更新
6、为什么会有1这种情况,这个问题也找精通drools引擎的大佬聊过,应该和他的设计理念有关,例如现在更新了 dt2 的 t,如果下边的规则都没有声明对属性t的推断,那么它就认为dt2更新与这些规则无关,所以就不触发了
后续:
目前,drools官网已经把这个问题当bug修复了,issues :https://issues.redhat.com/browse/DROOLS-7255?jql=project%20%3D%20DROOLS%20ORDER%20BY%20created%20DESC
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
