Nothing is impossible!

drools 规则引擎学习

Posted on By zhaofuchao

规则引擎基础

一、什么是规则引擎

​ 规则引擎是一种嵌套在应用程序种的组件,它实现了将业务规则从应用程序代码中分离出来, 使复杂的业务规则实现变得简单,也可以动态修改业务规则,从而快速的响应需求变更。java开源的规则引擎有:Drools、Easy Rules、Mandarax、IBM ILOG。内使用最为广泛并且开源的是Drools。

Drools 是一个基于Charles Forgy’s的RETE算法的易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。 业务分析师人员或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。对于开发人员则可以利用规则引擎实现业务规则与代码的解耦,避免更多的硬编码,达到规则变更时执行代码无影响的目的。

image-20210118144416404

Drools 是用Java语言编写的开放源码规则引擎,使用Rete算法对所编写的规则求值(

rete算法详细)。Drools允许使用声明方式表达业务逻辑。可以使用非XML的本地语言编写规则,从而便于学习和理解。并且,还可以将Java代码直接嵌入到规则文件中,这令Drools的学习更加吸引人。

简单案例

例如一个增加积分的列子,主要实现用户购买的金额达到一定量后送积分,具体的规则如下:

100元以下, 不加分 
100元-500元 加100分 
500元-1000元 加500分 
1000元 以上 加1000分

一般的java代码无非就是利用if else 语句进行实现。代码如下

if(amout <= 100){
	order.setScore(0);
    return;
}else if(amout > 100 && amout <= 500){
	order.setScore(100);
    return;
}else if(amout > 500 && amout <= 1000){
    order.setScore(500);
    return;
}else if(amout > 1000){
 	order.setScore(1000);
    return;
}

过了一段时间,活动规则变了,需求也随之变化,也是购物送积分,积分的基数是10分,要求购物金额超过100元加基数的1倍(10分),100元到500元之间加基数的5倍积分(10 X 5),购物金额在500元-1000元 之间加基数的10倍基金(10 X 10)…… 开发人员按照需求更改活动规则。

又过了一段时间,需求又变了,那么开发人员又要继续按照活动规则修改代码。

这样造成的结果是,代码越来愈复杂,修改以及维护起来越来越麻烦。因此,有必要找到一种技术可以将排班规则和代码解耦,不管规则如何变化,执行端不用动。规则引擎则是很好的选择。如上案例,对应的规则语句可以写成以下方式:

package point.rules

import com.demo.domain.model.Order

rule "zero"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout <= 100)
    then
        $s.setScore(0);
        System.out.println("不加积分");
        update($s);
end

rule "add100"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 100 && amout <= 500)
    then
        $s.setScore(100);
        System.out.println("加100积分");
        update($s);
end

rule "add500"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 500 && amout <= 1000)
    then
        $s.setScore(500);
        System.out.println("加500积分");
        update($s);
end

rule "add1000"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 1000)
    then
        $s.setScore(1000);
        System.out.println("加500积分");
        update($s);
end

rule "add10000"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 1000)
    then
        $s.setScore(1000);
        System.out.println("加500积分");
        update($s);
end

以上规则语句我们可以放入mysql等数据库中,如果规则有更新,可通过后台管理端对数据库中的规则语句进行操作,规则引擎直接读取最新的规则语句,即可达到动态更新规则脚本不影响业务的需求。

二、drools规则引擎语法概述

2.1. drools概念相关

  • 事实(Fact):对象之间及对象属性之间的关系
  • 规则(rule):是由条件和结论构成的推理语句,一般表示为When…Then…。一个规则的when部分称为LHS,then部分称为RHS。
  • 模式(module):就是指when语句的条件。这里when条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。

Drools通过 事实、规则和模式相互组合来完成工作。

2.2. drools基本语法

规则可以包含三个部分:

  • 属性部分:定义当前规则执行的一些属性等,比如是否可被重复执行、过期时间、生效时间等。
  • 条件部分,即LHS(左手定则),定义当前规则的条件,如 when Message(); 判断当前workingMemory中是否存在Message对象。
  • 结果部分,即RHS(右手定则),这里可以写普通java代码,即当前规则条件满足后执行的操作,可以直接调用Fact对象的方法来操作应用。
package com.test

import java.util.List

rule "name"
    no-loop true
    when
    	 eval(true)
         $customer :Customer()
         $message:Message(status==0)
    then
    	System.out.println("hello");
    	$message.setStatus(1);
    	update($message);
end

2.2.1 关键字

  • package: 与Java语言类似,drl的头部需要有package和import的声明,package不必和物理路径一致,这里只是一个逻辑区分。
  • import: 导入java Bean的完整路径,也可以将Java静态方法导入调用。
  • rule: 规则名称,需要保持唯一 。
  • no-loop: 定义当前的规则是否允许多次循环执行,默认是 false允许循环执行,也就是当前的规则只要满足条件,可以无限次执行。
  • date-expires:设置规则的过期时间,默认的时间格式:“日-月-年”,中英文格式相同,
  • date-effective:设置规则的生效时间,时间格式同上。
  • duration:规则定时,duration 3000 ,3秒后执行规则
  • salience: 用来设置规则执行的优先级,salience 属性的值是一个数字,数字越大执行优先级越高,,同时它的值可以是一个负数。默认情况下,规则的 salience 默认值为 0。如果不设置规则的 salience 属性,那么执行顺序是随机的。
  • when: 条件语句,就是当到达什么条件的时候执行
  • then: 根据条件的结果,来执行什么动作
  • end: 规则结束

更多关键字详细解析

2.2.2 规则的条件部分,即LHS部分

when
         eval(true)
         $customer:Customer()
         $message:Message(status==0)
  • eval(true):是一个默认的api,true 无条件执行,类似于 while(true)
  • $customer:Customer():表示当前的workingMemory存在Customer类
  • $message:Message(status==0) 这句话表示:当前的workingMemory存在Message类型并且status属性的值为0的Fact对象,这个对象通常是通过外部java代码插入或者自己在前面已经执行的规则的RHS部分中insert进去的。

2.2.3 规则的结果部分,RHS部分

then
       System.out.println("hello");//会在控制台打印出hello
    	$message.setStatus(1);//设置message得status状态为1
    	update($message);//更新工作内存中得message对象
    	console();
end

insert:往当前workingMemory中插入一个新的Fact对象,会触发规则的再次执行,除非使用no-loop限定;

update:更新将当前workingMemory中Fact对象更新

modify:修改,与update语法不同,结果都是更新操作

retract:删除当前workingMemory中Fact对象

function:定义一个方法,如下

function void console() {
   System.out.println("hello function");
   StringUtils.getId();// 调用外部静态方法,StringUtils必须使用import导入,getId()必须是静态方法
}

2.3 drl文件

drools脚本语句一般写在.drl文件中,必要时可以动态生成.drl文件。文件一般放在src/main/resources目录下,可以在resources目录下新建文件夹

package point.rules

import com.xiaolyuh.domain.model.Order

rule "addZero"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout <= 100)
    then
        $s.setScore(0);
        System.out.println("不加积分");
        update($s);
end

rule "add100"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 100 && amout <= 500)
    then
        $s.setScore(100);
        System.out.println("加100积分");
        update($s);
end

rule "add500"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 500 && amout <= 1000)
    then
        $s.setScore(500);
        System.out.println("加500积分");
        update($s);
end

最后需要有一个配置文件告诉程序.drl规则文件在哪里,在drools中这个文件就是kmodule.xml,放置到resources/META-INF目录下,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">

    <!-- 这里的packages属性就是规则文件的文件路径 -->
    <kbase name="point_rule" packages="rules.point">
        <ksession name="point_ksession"/>
    </kbase>

</kmodule>

以下对配置说明进行简单说明:

  • Kmodule: 中可以包含一个到多个 kbase,分别对应 drl 的规则文件。
  • Kbase 需要一个唯一的 name,可以取任意字符串。
  • packages: 为drl文件所在resource目录下的路径。注意区分drl文件中的package与此处的package不一定相同。多个包用逗号分隔。默认情况下会扫描 resources目录下所有(包含子目录)规则文件。
  • kbase的default属性:表示当前KieBase是不是默认的,如果是默认的则不用名称 就可以查找到该 KieBase,但每个 module 最多只能有一个默认的KieBase。
  • kbase的ksession: kbase下面可以有一个或多个 ksession,ksession 的 name 属性必须设置,且必须唯一。

2.4规则表文件

drools规则引擎另外一个典型的用法,就是使用规则表。

drolls提供的api中有负责解析excel文件,根据excel对应的列对应的值创建KnowledgeBase,然后将它丢给session执行,执行的参数和结果都在param里面。

image-20210119084708492

三、drools规则引擎架构

3.1 drools

规则引擎的大脑实际上就是一个推理引擎,首先是加载规则,用于匹配facts和rules;在工作内存中引擎将事实、数据与产生式规则进行匹配(模式匹配),以推出结论;当匹配被找到,由agenda模块负责将rule的action执行。Actions经常会改变facts的状态,或者在应用上执行一些“外部“action。

image-20210118151255304

Facts

Facts 是规则可以访问的任意的java对象。规则引擎中的 facts 并不是“ clone ” facts ,它只是持有到你的应用中数据的引用。 Facts 是你的应用数据。 String 和其他没有 getter 和 setter 的类不是有效的 Fact 。

Rule Base

Rule Base包含了多个将被使用的规则包(packages of rules)。一个Rule Base是可以序列化的,所以它可以被配置到JNDI或其他类似的服务。通常,第一次使用时,一个Rule Base被创建并缓存。Rule Base用Rule Base Factory来实例化,默认返回一个 Rule Base。

Working Memory

Working Memory 是运行时规则引擎的主要类。它保持了所有被 asserted 进 WorkingMemory 的数据的引用,直到取消( retracted )。 WorkingMemory 是有状态对象。它们的生命周期可长可短。如果从一个短生命周期的角度来同一个引擎进行交互,意味着你可以使用 RuleBase 对象来为每个 session 产生一个新的 WorkingMemory,然后在结束 session 后 discard 这个 WorkingMemory (产生一个 WorkingMemory 是一个廉价的操作)。另一种形式,就是在一个相当长的时间中(例如一个 conversation ),保持一个 WorkingMemory ,并且对于新的facts 保持持续的更新。当你希望 dispose 一个 WorkingMemory 的时候,最好的实践就是调用 dispose() 方法,此时 RuleBase 中对它的引用将会被移除(尽管这是一个弱引用)。不管怎样最后它将会被当成垃圾收集掉。

Agenda

Agenda 是 RETE 的一个特点。在一个 WorkingMemory Action 发生时,可能会有多条规则发生完全匹配。当一条规则完全匹配的时候,一个 Activation 就被创建(引用了这条规则和与其匹配的 facts ),然后放进 Agenda中。 Agenda 通过使用冲突解决策略( Conflict Resolution Strategy )来安排这些 Activations 的执行。

3.2 kie

KIE(Knowledge Is Everything)是jBoss里面一些相关项目的统称,例如jBPM和Drools ,这些项目都有一定的关联关系,并且存在一些通用的API,比如说涉及到构建(building)、部署(deploying)和加载(loading)等方面的

image-20210118163312252

这些API就都会以KIE作为前缀来表示这些是通用的API 。

kie公共的api 如KieServices、KieContainer、KieSession。

KieServices

通过KieServices.Factory.get()方式获得,是一个单例的、线程安全的,为其他Kie工具提供服务 KieServices是Kie项目的中心,通过其可以获取的各种对象来完成规则构建、管理和执行等操作 其中有的方法分为两大类:getX()和newX(),其中,get只会返回一个对应单例对象的引用,new则会重新创建一个对象

KieRepository

是一个单例的存放所有KieModule的仓库,无论KieModule是存放在Maven仓库中还是动态配置的 KieModule由kmodule.xml文件定义,也可以由代码来定义

KieContainer

从KieServices中获得,其会借助KieProject来初始化、构造KieModule并将放入KieRepository中 是一个给定的KieModule中所有KieBase的存放容器

KieProject

KieContainer可以通过KieProject来查找KieModule定义的信息,并根据这些信息构造KieBase和KieSession

ClasspathKieProject

ClasspathKieProject实现了KieProject接口,它提供了根据类路径中的META-INF/kmodule.xml文件构造KieModule的能力

KieBase

KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,但是不包含运行时的数据

KieSession

KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建 KieContainer创建KieSession是一种较为方便的做法,其实他本质上是从KieBase中创建出来。

利用kie可以快速搭建一个drools规则引擎;

在操作drools时经常使用kie api来进行,这些api之间的关系如下图:

image-20210118165057095

具体的构建drools引擎的代码如下:

// 通过单例创建KieServices
KieServices kieServices = KieServices.Factory.get();
// 获取KieContainer
KieContainer kieContainer = kieServices.getKieClasspathContainer();

// 获取KieBase
KieBase kieBase = kieContainer.getKieBase();
// 创建KieSession
KieSession kieSession = kieContainer.newKieSession("session-name");