1.规则引擎

规则引擎是由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。开源的代表是Drools,商业的代表是Visual Rules ,I Log

复杂企业级项目运营及维护过程中随外部条件不断变化的业务规则(business logic),
迫切需要分离商业决策者的商业决策逻辑和应用开发者的技术决策,
并把这些商业决策放在中心数据库或其他统一的地方,让它们能独立运行;可以动态地管理和修改规则模板从而提供软件系统的柔性和适应性

2.Drools规则引擎

Drools具有一个易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。

Drools是为Java量身定制的基于Charles Forgy的RETE算法的规则引擎的实现。支持Java代码直接嵌入到规则文件中,使得商业规则有了更自然的表达。

Drools主要分为两个部分:一是Drools规则,二是Drools规则的解释执行。规则的编译与运行要通过Drools 提供的相关API 来实现。而这些API 总体上游可分为三类:规则编译、规则收集和规则的执行

3.Drools规则引擎优点

3.1申明式编程

明确规则机制让我们可以“做什么”

规则机制的核心优势在于可以简化对于复杂问题的逻辑表述,并对这些逻辑进行验证。

规则机制提供一个如何解决问题的说明,并说明每个决策的是如何得出的

3.2业务逻辑和数据分离

业务数据存储业务对象中,业务决策存储规则中,独立运行

3.3速度和可扩展性

使用网络算法(Rete algorithm),跳跃算法(Leaps algorithm),提供了非常高效的方式根据业务对象的数据匹配规则

3.3.1 Rete算法

Rete算法是一个快速的模式匹配算法,它通过存储关于规则的信息而获得速度

Rete算法的基本思想是保存过去匹配过程中留下的全部信息,以空间代价来换取执行效率 。对每一个模式 ,附加一个匹配元素表来记录WorkingMemory中所有能与之匹配的元素。当一个新元素加入到WorkingMemory时, 找出所有能与之匹配的模式, 并将该元素加入到匹配元素表中; 当一个无素从WorkingMemory中删除时,同样找出所有与该元素匹配的模式,并将元素从匹配元素表中删除。 Rete算法接受对工作存储器的修改操作描述 ,产生一个修改冲突集的动作

Rete的高效率主要来自两个重要的假设

时间冗余性:facts在推理过程中的变化是缓慢的, 即在每个执行周期中,只有少数的facts发生变化,因此影响到的规则也只占很小的比例。所以可以只考虑每个执行周期中已经匹配的facts.
结构相似性:许多规则常常包含类似的模式和模式组。

规则引擎java drools 规则引擎drools_规则引擎java drools

 

Rete算法的步骤如下:
1.将初始数据(fact)输入Working Memory。
2.使用Pattern Matcher比较规则(rule)和数据(fact)。
3.如果执行规则存在冲突(conflict),即同时激活了多个规则,将冲突的规则放入冲突集合。
4.解决冲突,将激活的规则按顺序放入Agenda。
5.使用规则引擎执行Agenda中的规则。

重复步骤2至5,直到执行完毕所有Agenda中的规则

3.3.2 Leaps 算法

前向推理引擎,包括LEAPS,都包括了匹配-选择-执行(match-select-action)循环。即,确定可以匹配的规则,选择某个匹配 的元 组,此元组相应的规则动作被执行。重复这一过程,直到某一状态(如没有更多的规则动作)。RETE和TREAT匹配算法速度慢的原因是,它们把满足规则条 件的元组都实例化。Leaps算法的最大的改进就是使用一种"lazy"的方法来评估条件(conditions),即仅当必要时才进行元组的实例化。这一改进极大的减少了前向推理引擎的时空复杂度,极大提高了规则执行速度。

Leaps算法将所有的 asserted 的 facts ,按照其被 asserted 在 Working Memory 中的顺序( FIFO ),放在主堆栈中。它一个个的检查 facts ,通过迭代匹配 data type 的 facts 集合来找出每一个相关规则的匹配。当一个匹配的数据被发现时,系统记住此时的迭代位置以备待会的继续迭代,并且激发规则结果( consequence )。当结果( consequence )执行完成以后,系统就会继续处理处于主堆栈顶部的 fact 。如此反复。
Leaps算法的效率可以比Rete算法和Tread算法快几个数量级

3.4规则集中化

通过使用规则,可以创建出一个可运行的知识库。这就意味着对于业务规则可以具备良好的可阅读性,可以起到文档的作用

3.5易懂的规则

通过模型对象以及模型说明语言(Domain Specific Languages)能让你使用很接近自然语言的方式为领域问题建模。借助于这些方式,可让非技术领域的业务使用来描述业务问题

4.Drools规则模板

package rules;                                                // 规则包名路径

import com.xxx.vo.ProductManageVo            // 引入类
import com.xxx.BackList
import com.xxx.service.ProductRiskService
import java.util.HashMap
import java.util.Map

dialect "java"                                                // 定于语言,标识规则中的代码表达式
global ProductRiskService productRiskService                  // 引入外部服务

rule "1001_product"                                           // 规则名称,全局唯一
    salience 1001                                             // 规则优先级,值越大越先执行
    lock-on-active true                                       // 事件是否重复执行该规则 true 至执行一次
    when                                                      // 条件判断关键字
        event:ProductManageVo()                               // 条件判断,是否需要进入action-判断事件对象是否是ProductManageVo对象并赋值于event
    then                                                      // 执行事件关键字
        if(event.getThirdPrice().doubleValue()/event.getTopPrice() <= 0.5){  // 事件业务逻辑,支持java编码
            Map<String,Object> ruleContent = new HashMap<>();
             ruleContent.put("thirdPrice",event.getThirdPrice().doubleValue());
             ruleContent.put("topPrice",event.getTopPrice());
             ruleContent.put("message","三方价格小于TOP原价5折");
            productRiskService.saveProductRiskInfo("1001_product",event,ruleContent);
        }
     end                                                           // 规则结束关键字


4.1无约束匹配模式

rule "1002"
     when
        ProductManageVo()
     then
        System.out.println("无约束的匹配模式,匹配ProductManageVo对象即可");
     end

4.2有条件匹配模式

rule "1003"
     when
        ProductManageVo(topPrice>0)
     then
        System.out.println("有条件匹配模式,ProductManageVo.topPrice>0即可");
     end

4.3匹配并绑定属性模式

rule "1004"
     when
        event:ProductManageVo($topPrice : topPrice>0)
     then
        System.out.println("有条件匹配模式,ProductManageVo.topPrice>0;" +"并赋值属性:"+event.getBarcode()+",$topPrice="+$topPrice);
     end

5.Drools支持的条件约束

rule "1005"
     when
        ProductManageVo(thirdPrice!=null&&topPrice>0)
     then
        System.out.println("条件约束表达式");
     end

约束

描述

!.

使用此运算符可以以空安全的方式取消引用属性。!.运算符左侧的值不能为null(解释为!= null)

[]

按List索引访问值或Map按键访问值

<,<=,>,>=

在具有自然顺序的属性上使用这些运算符

==, !=

在约束中使用这些运算符作为equals()和!equals()方法

&&,||

组合关系条件

matches,not matches

使用这些运算符可以指示字段与指定的Java正则表达式匹配或不匹配

contains,not contains

使用这些运算符可以验证Array或字段是否包含或不包含指定值

memberOf,not memberOf

使用这些运算符可以验证字段是否为定义为变量Array的成员

soundslike

使用英语发音来验证单词是否具有与给定值几乎相同的声音(类似于该matches运算符)

in,notin

使用这些运算符可以指定一个以上的可能值来匹配约束(复合值限制)

6.Drools集合应用

6.1从集合中取数据

rule "1006"
     when
        $event:ProductManageVo()
         $backList : BackList(type==0) from $event.backLists
     then
       System.out.println("遍历集合数据:"+$backList);
     end

规则引擎java drools 规则引擎drools_java_02

 

7.SpringBoot引入Drools.jar

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>6.4.0.Final</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-ci</artifactId>
    <version>6.4.0.Final</version>
</dependency>


8.Java Demo

RiskConfig表结构

CREATE TABLE `r_risk_config` (
   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
   `risk_code` varchar(128) NOT NULL COMMENT '规则编码',
   `risk_name` varchar(256) DEFAULT NULL COMMENT '规则名称',
   `risk_content` text COMMENT '规则模板',
   `risk_key` varchar(64) DEFAULT NULL COMMENT '规则模板md5,验证是否需要重新加载',
   `risk_type` tinyint(3) DEFAULT '0' COMMENT '0-商品,1-订单',
   `risk_level` tinyint(3) DEFAULT '0' COMMENT '风控等级',
   `is_send_email` tinyint(3) DEFAULT '0' COMMENT '是否发送邮件0-否,1-是',
   `is_wx_news` tinyint(3) DEFAULT '1' COMMENT '是否发送告警信息0-否,1-是',
   `is_fuse` tinyint(3) DEFAULT '0' COMMENT '是否熔断流程0-否,1-是',
   `is_black_list` tinyint(3) DEFAULT '0' COMMENT '是否添加黑名单0-否,1-是',
   `status` tinyint(3) NOT NULL DEFAULT '1' COMMENT '是否生效0-否,1-是',
   `create_time` datetime DEFAULT NULL COMMENT '创建时间',
   `creator` bigint(20) DEFAULT NULL COMMENT '创建人',
   `modifier` bigint(20) DEFAULT NULL COMMENT '修改人',
   `modify_time` datetime DEFAULT NULL COMMENT '修改时间',
   `dr` tinyint(1) DEFAULT '0' COMMENT '删除标志',
   `ver` int(11) DEFAULT '1' COMMENT '版本号',
   `ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '时间戳',
   PRIMARY KEY (`id`),
   UNIQUE KEY `uni_risk_code` (`risk_code`)
 ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='风控规则表';

规则模板RiskConfig配置

规则引擎java drools 规则引擎drools_drools_03

 

@Service
@Slf4j
public class KieServiceImpl implements KieService {

     private StatelessKieSession kieSession;

     private KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();

     @Resource
     private RiskConfigService riskConfigService;
     //商品规则处理
    @Resource
     private ProductRiskService productRiskService;

     @Override
     public void loadRules(String ruleCode) {
         RiskConfig riskConfig = riskConfigService.queryRiskConfig(ruleCode);
         if(riskConfig!=null){
             loadRule(ruleCode,riskConfig.getRiskContent());
        }
    }

     /**
     * 单一规则下线
     *
     * @param ruleName
     */
    @Override
     public void removeRule(String ruleName) {
         if (kbase.getRule("rules", ruleName) != null) {
             kbase.removeRule("rules", ruleName);
             log.info("remove rule: rules." + ruleName);
             kieSession = kbase.newStatelessKieSession();
             printRules();
        } else {
             log.error("no rule: rules." + ruleName);
        }
    }

     /**
     * 规则引擎执行
     *
     * @param object
     */
    @Override
     public void execute(Object object) {
         this.kieSession.execute(object);
    }

     /**
     * drools全局服务变量
     */
    @PostConstruct
     public void init() {
         loadRulesAll();
    }

     /**
     * @description: 加载所有规则模板
     * @param:
     * @return:
    */
    private void loadRulesAll() {
         RiskConfig config = new RiskConfig();
         List<RiskConfig> riskConfigList = riskConfigService.queryRiskConfigs(config);
         if(riskConfigList!=null){
             for(RiskConfig riskConfig : riskConfigList){
                 loadRule(riskConfig.getRiskCode(),riskConfig.getRiskContent());
            }
        }
    }

     /**
     * 单一规则上线
     *
     * @param ruleCode
     * @param ruleContent
     */
    private void loadRule(String ruleCode,String ruleContent){
         try {
             KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
             kbuilder.add(ResourceFactory.newByteArrayResource(ruleContent.getBytes()), ResourceType.DRL);
             if(kbuilder.hasErrors()){
                 log.error("加载规则模板引擎异常{},{}",ruleCode,kbuilder.getErrors());
                 return;
            }
             kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
             kieSession = kbase.newStatelessKieSession();
             printRules();
             setGlobal();
        }catch (Exception e){
             log.error("加载规则模板引擎异常",e);
        }
    }

     /**
     * @description: 设置全区实现对象
     * @param:
     * @return:
    */
    private void setGlobal(){
         kieSession.setGlobal("productRiskService", productRiskService);
    }

     /**
     * 打印规则
     */
    private void printRules() {
         log.info("print rule start: ---------");
        kbase.getKnowledgePackages().forEach(knowledgePackage ->
                knowledgePackage.getRules().forEach(rule ->
                         log.info("print rule: " + knowledgePackage.getName() + "." + rule.getName())));
         log.info("print rule end: ---------");
    }
}