背景
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.Dt1
rule "a_1"
salience 100
enabled true
ruleflow-group "group_a"
lock-on-active true
dialect "mvel"
when
dt1 : Dt1(a == 1)
dt2 : Dt2( )
then
System.out.println("a_1 has been MatchFired!");
modify( dt2 ) {
setT("YES")
}
end
rule "b_1"
salience 99
enabled true
ruleflow-group "group_b"
lock-on-active true
dialect "mvel"
when
dt1 : Dt1( )
dt2 : Dt2( )
eval(dt2.getT()=="YES")
then
System.out.println("b_1 has been MatchFired!");
end
rule "b_2"
salience 98
enabled true
ruleflow-group "group_b"
lock-on-active true
dialect "mvel"
when
dt1 : Dt1( )
dt2 : Dt2( t:t )
eval(t=="YES")
then
System.out.println("b_2 has been MatchFired!");
end
rule "b_3"
salience 97
enabled true
ruleflow-group "group_b"
lock-on-active true
dialect "mvel"
when
dt2 : Dt2( )
eval(dt2.getT()=="YES")
then
System.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,即dt2
PropagationEntry 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 0
group = 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 inactive
if (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 compilation
evaluateQueriesForRule( 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