探究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.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_2b_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看堆栈信息
在这里插入图片描述

beta规则执行的堆栈信息
进到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是自增的。有兴趣的可自行跟一下源码。
beta节点更新逻辑
但是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

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值