作者:京东物流 李振 康睿 刘斌 王北永

、 规则引擎业务应用背景

业务逻辑中经常会有一些冗长的判断,需要写特别多的if else,或者一些判断逻辑需要经常修改。这部分逻辑如果以java代码来实现,会面临代码规模控制不住,经常需要修改逻辑上线等多个弊端。这时候我们就需要集成规则引擎对这些判断进行线上化的管理

二、规则引擎选型

目前开源的规则引擎也比较多,根据原有项目依赖以及短暂接触过的规则引擎,我们着重了解了一下几个

drools:

-社区活跃,持续更新

-使用广泛

-复杂

-学习成本较高

https://github.com/kiegroup/drools

easy-rule:

-简单易学

-满足使用诉求

-长时间未发布新版

https://github.com/j-easy/easy-rules

easycode:

-京东物流同事维护的平台

-基于flowable dmn实现

-配置简单直观

-已有大量系统使用

总结:

  1. 简单配置型规则可以接入easycode,平台提供配置页面,通过jsf交互。
  1. 复杂规则,需要动态生成规则,easycode目前还不支持。drools从流行度及活跃度考虑,都比easy-rule强,所以选择drools。

三、 drools简单示例

3.1 引入依赖

  1. <dependency>
  2. <groupId>org.kie</groupId>
  3. <artifactId>kie-spring</artifactId>
  4. <version>${drools.version}</version>
  5. </dependency>

3.2 写drl文件

我们写一个简单的demo

规则为:

匹配一个sku对象

0<价格<=100 10积分

100<价格<=1000 100积分

1000<价格<=10000 1000积分

在resources文件夹下新增 rules/skupoints.drl 文件 内容如下

  1. package com.example.droolsDemo
  2. import com.example.droolsDemo.bean.Sku;
  3. // 10积分
  4. rule "10_points"
  5. when
  6. $p : Sku( price > 0 && price <= 100 )
  7. then
  8. $p.setPoints(10);
  9. System.out.println("Rule name is [" + drools.getRule().getName() + "]");
  10. end
  11. // 100积分
  12. rule "100_points"
  13. when
  14. $p : Sku( price > 100 && price <= 1000 )
  15. then
  16. $p.setPoints(100);
  17. System.out.println("Rule name is [" + drools.getRule().getName() + "]");
  18. end
  19. // 1000积分
  20. rule "1000_points"
  21. when
  22. $p : Sku( price > 1000 && price <= 10000 )
  23. then
  24. $p.setPoints(1000);
  25. System.out.println("Rule name is [" + drools.getRule().getName() + "]");
  26. end

3.3 使用起来

  1. @Test
  2. public void testOneSku() {
  3. Resource resource = ResourceFactory.newClassPathResource("rules/skupoints.drl");
  4. KieHelper kieHelper = new KieHelper();
  5. kieHelper.addResource(resource);
  6. KieBase kieBase = kieHelper.build();
  7. KieSession kieSession = kieBase.newKieSession();
  8. Sku sku1 = new Sku();
  9. sku1.setPrice(10);
  10. kieSession.insert(sku1);
  11. int allRules = kieSession.fireAllRules();
  12. kieSession.dispose();
  13. System.out.println("sku1:" + JSONObject.toJSONString(sku1));
  14. System.out.println("allRules:" + allRules);
  15. }
  16. @Test
  17. public void testOneSku2() {
  18. Resource resource = ResourceFactory.newClassPathResource("rules/skupoints.drl");
  19. KieHelper kieHelper = new KieHelper();
  20. kieHelper.addResource(resource);
  21. KieBase kieBase = kieHelper.build();
  22. StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();
  23. Sku sku1 = new Sku();
  24. sku1.setPrice(10);
  25. statelessKieSession.execute(sku1);
  26. System.out.println("sku1:" + JSONObject.toJSONString(sku1));
  27. }

3.4 输出

3.5 总结

如上,我们简单使用drools,仅需要注意drl文件语法。根据drl文件生成规则的工作内存,通过KieSession或者StatelessKieSession与工作内存交互。整个流程并不复杂。注意 KieHelper仅是在演示中简单使用,demo中包含使用bean来管理容器的方式,即便在简单使用场景也不应通过 KieHelper来重复加载规则。

但是这样并不能满足我们线上化判断,或者频繁更改规则的诉求。于是我们在实践中需要对drools更高阶的使用方式。

四、 drools动态化实践

从以上简单demo中我们可以看出,规则依赖drl文件存在。而业务实际使用中,需要动态对规则进行修改,无法直接使用drl文件。

以下是我了解过的四种动态的方案:

  • drt文件,创建模板,动态生成drl文件,也是我们目前所用的方式。
  • excel文件导入,实际上和模板文件类似,依然无法直接交给业务人员来使用。
  • 自己拼装String,动态生成drl文件,网上大多数博文使用方式,过于原始。
  • api方式,drools的api方式复杂,使用需要对drl文件有足够的了解。

最后介绍以下drools在项目中的实际使用方式

4.1 配置规则

我们的业务场景可以理解为多个缓冲池构成的一个网状结构。

示例如下:

上图中每个方块为一个缓冲池,每条连线为一条从A缓冲池流向B缓冲池的规则。实际场景中缓冲池有数百个,绝大多数都有自己的规则,这些规则构成一张复杂的网络。基于业务诉求,缓冲池的流向规则需要经常变动,我们需要在业务中能动态改变这些连线的条件,或者改变连线。在这种情况下,如果使用静态的drl文件来实现这些规则,需要数百规则文件,维护量大,且每次修改后使规则生效的代价较大。在此背景下,我们尝试drools高阶应用,既规则动态化实践。

我们在创建缓冲池的页面中加入了流向规则的创建环节。每个缓冲池维护自己的流向规则,即为自己的一根连线。如下图:

4.2 动态生成drl

drt文件内容:

(实际业务模板中比这个复杂,有一定校验及业务逻辑,此处做了简化)

  1. template header
  2. // 模板需要使用的参数
  3. id
  4. cluePoolId
  5. sourceList
  6. cooperateTypeList
  7. regionId
  8. secondDepartmentId
  9. battleId
  10. outCluePoolId
  11. amountCompareFlag
  12. amount
  13. salience
  14. package rulePoolOut
  15. // 全局对象
  16. global java.util.List list;
  17. global java.util.List stopIdList;
  18. global java.util.List ruleIdList;
  19. // 引入的java类
  20. import com.example.drools.bean.ClueModel
  21. import org.springframework.util.CollectionUtils
  22. import org.apache.commons.lang3.StringUtils;
  23. import java.lang.Long
  24. template "CluePoolOut"
  25. // 规则名称
  26. rule "clue_pool_@{cluePoolId}_@{id}"
  27. // 参数 标识当前的规则是否不允许多次循环执行
  28. no-loop true
  29. // 参数 优先级
  30. salience @{salience}
  31. // 参数 规则组 本组规则只能有一个生效
  32. activation-group "out_@{cluePoolId}"
  33. // 匹配的LHS
  34. when
  35. $clue:ClueModel(cluePoolId == @{cluePoolId})
  36. ClueModel(CollectionUtils.isEmpty(@{sourceList}) || source memberOf @{sourceList})
  37. ClueModel(CollectionUtils.isEmpty(@{cooperateTypeList}) || cooperateType memberOf @{cooperateTypeList})
  38. ClueModel(secondDepart == @{secondDepartmentId})
  39. ClueModel(regionNo == @{regionId})
  40. ClueModel(battleId == @{battleId})
  41. ClueModel(null != estimateOrderCount && (Long.valueOf(estimateOrderCount) @{amountCompareFlag} Long.valueOf(@{amount})))
  42. // 如果配置要执行的RHS 支持java语法
  43. then
  44. ruleIdList.add(@{id});
  45. $clue.setCluePoolId(Long.valueOf(@{outCluePoolId}));
  46. list.add(@{outCluePoolId});
  47. update($clue);
  48. }
  49. end
  50. end template

生成drl内容: 根据一个队列及模板的路径进行drl内容的生成

  1. List<CrmCluePoolDistributeRuleBusinessBattleVO> ruleCenterVOS = new ArrayList<>();
  2. CrmCluePoolDistributeRuleBusinessBattleVO vo = new CrmCluePoolDistributeRuleBusinessBattleVO();
  3. vo.setCooperateTypeList(Lists.newArrayList(1, 2, 4));
  4. vo.setAmountCompareFlag(">");
  5. vo.setAmount(100L);
  6. ruleCenterVOS.add(vo);
  7. String drl = droolsManager.createDrlByTemplate(ruleCenterVOS, "rules/CluePoolOutRuleTemplate.drt");
  8. public String createDrlByTemplate(Collection<?> objects, String path) {
  9. ObjectDataCompiler compiler = new ObjectDataCompiler();
  10. try (InputStream dis = ResourceFactory.newClassPathResource(path, this.getClass()).getInputStream()) {
  11. return compiler.compile(objects, dis);
  12. } catch (IOException e) {
  13. log.error("创建drl文件失败!", e);
  14. }
  15. return null;
  16. }

4.3 加载drl

上边的简单示例中,我们使用了KieHelper 来加载规则文件至工作内存中。实际上我们不可能在每次匹配中重新加载所有规则文件,所以我们可以单例的使用规则容器,通过以下方式或者也可以使用@Bean等方式来管理容器。

  1. private final KieServices kieServices = KieServices.get();
  2. // kie文件系统,需要缓存,如果每次添加规则都是重新new一个的话,则可能出现问题。即之前加到文件系统中的规则没有了
  3. private final KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
  4. // 需要全局唯一一个
  5. private KieContainer kieContainer;

通过将内容写入 kieFileSystem然后重新加载整个 kieBase即可重新加载规则,但是这种行为比较重,代价较大

也可以通过 kieBase新增一个文件来进行加载,代价小,但是同步各个实例的代价较大。

  1. KnowledgeBaseImpl kieBase = (KnowledgeBaseImpl)kieContainer.getKieBase(kieBaseName);
  2. KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
  3. Resource resource = ResourceFactory.newReaderResource(new StringReader(ruleContent));
  4. builder.add(resource,ResourceType.DRL);
  5. if (builder.hasErrors()) {
  6. throw new RuntimeException("增加规则失败!" + builder.getErrors().toString());
  7. }
  8. kieBase.addPackages(builder.getKnowledgePackages());

4.4 匹配

通过 StatelessKieSession与规则引擎交互

  1. // 获取一个链接
  2. StatelessKieSession kieSession = droolsManager.getStatelessKieSession(RuleTemplateEnum.CLUE_POOL_OUT_RULE.getKieBaseName());
  3. // 创建全局变量对象
  4. List<Long> list = new ArrayList<>();
  5. List<Long> stopIdList = Lists.newArrayList();
  6. List<String> result = new ArrayList<>();
  7. List<Long> ruleIdList = new ArrayList<>();
  8. // 塞入全局变量
  9. kieSession.setGlobal("ruleIdList", ruleIdList);
  10. kieSession.setGlobal("list", list);
  11. kieSession.setGlobal("stopIdList", stopIdList);
  12. kieSession.setGlobal("result", result);
  13. // 执行规则
  14. kieSession.execute(clueModel);

如果使用 KieSession则需要在使用完成后进行关闭

  1. kieSession.insert(clueModel);
  2. kieSession.fireAllRules();
  3. kieSession.dispose();

在执行规则的过程中可以加入各种监听器对过程中各种变化进行监听。篇幅原因交给各位去探索。

五、 总结

从上边的流程中我们体验了动态规则的创建以及使用。动态规则满足了我们规则动态变化,规则统一管理的诉求。

我也总结了在这种使用方式下drools的几个优缺点。

优点:

  • 规则动态化方便
  • 在工作内存中匹配规则性能好
  • 几乎可以满足所有的规则需求
  • 内置方法丰富完善

缺点:

  • 分布式一致性需要自行处理
  • 需要研发了解drl语法
  • 学习曲线陡峭
  • 匹配过程监控手段需要自行实现

drools规则动态化实践的更多相关文章

  1. Drools规则引擎实践直白总结

    目录 1. 创建Drools环境(引入Drools相关依赖包.现在都流行spring boot,故最简单有效的依赖才是最好的,kie-spring内部自行依赖了drools相关核心的依赖包) 2. 了 ...

  2. Drools规则引擎-如果判断某个对象中的集合是否包含指定的值

    规则引擎集合相关处理 在实际生产过程中,有很多关于集合的处理场景,比如一个Fact对象中包含有一个集合,而需要判断该集合是否包含某个值.而Drools规则引擎也提供了多种处理方式,比如通过from.c ...

  3. Drools规则

    1.实现业务逻辑和业务规则的分离,实现业务规则的集中管理 2.可以动态的改变业务规则,从而快速响应需求变更 3.业务分析人员也可以参与编辑.维护系统的业务规则 fact:一个普通的JavaBean插入 ...

  4. 线上故障排查——drools规则引擎使用不当导致oom

    事件回溯 1.7月26日上午11:34,告警邮件提示:tomcat内存使用率连续多次超过90%: 2.开发人员介入排查问题,11:40定位到存在oom问题,申请运维拉取线上tomcat 内存快照dum ...

  5. Drools规则引擎入门指南(一)

    最近项目需要增加风控系统,在经过一番调研以后决定使用Drools规则引擎.因为项目是基于SpringCloud的架构,所以此次学习使用了SpringBoot2.0版本结合Drools7.14.0.Fi ...

  6. drools规则引擎初探

    https://www.cnblogs.com/yuebintse/p/5767996.html 1.drools是什么 Drools是为Java量身定制的基于Charles  Forgy的RETE算 ...

  7. Drools规则引擎

    一.简介 Drools is a Business Rules Management System (BRMS) solution. It provides a core Business Rules ...

  8. Drools 规则引擎环境搭建

    一.关于 drools 规则引擎 前面写过一篇 Drools 规则引擎相关的文章,这篇文章主要记录一下规则引擎的环境搭建和简单示例.不熟悉 drools 的朋友可以看看这篇文章: 自己写个 Drool ...

  9. Spring Boot+Drools规则引擎整合

    目的 官方的Drools范例大都是基于纯Java项目或Maven项目,而基于Spring Boot项目的很少. 本文介绍如何在Spring Boot项目上加上Drools规则引擎. POM依赖 POM ...

  10. Drools规则引擎-memberOf操作

    场景 规则引擎技术讨论2群(715840230)有同学提出疑问,memberOf的使用过程中如果,memberOf之后的参数不是集合也不是数组,而是格式如"1,2,3,4"的字符串 ...

随机推荐

  1. ModelBox姿态匹配:抖抖手动动脚勤做深呼吸

    摘要:本案例使用Windows版本的ModelBox SDK进行二次开发,主要是针对姿态匹配案例开发实践. 本文分享自华为云社区<姿态匹配:抖抖手动动脚勤做深呼吸>,作者:吴小鱼. 在之前 ...

  2. 整理混乱的头文件,我用include what you use

    摘要:使用include-what-you-use(iwyu/IWYU)清理冗余头文件,补充必要头文件. 本文分享自华为云社区<用include what you use拯救混乱的头文件> ...

  3. 使用Plist编辑器——简单入门指南

      本指南将介绍如何使用Plist编辑器.您将学习如何打开.编辑和保存plist文件,并了解plist文件的基本结构和用途.跟随这个简单的入门指南,您将掌握如何使用Plist编辑器轻松管理您的plis ...

  4. LAS Spark 在 TPC-DS 的优化揭秘

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 文章主要介绍了火山引擎湖仓一体分析服务 LAS Spark(下文以 LAS Spark 指代)在 TPC-DS 上 ...

  5. WebRTC SDP 详解和剖析

    WebRTC 是 Web Real-Time Communication,即网页实时通信的缩写,是 RTC 协议的一种 Web 实现,项目由 Google 开源,并和 IETF 和 W3C 制定了行业 ...

  6. leaflet 绘制 带箭头的线

    箭头不是画的线段,是贴的图标,再按方向旋转一下. 代码: //添加箭头线 function addLineDirection(polylinePointArr, source, target) { v ...

  7. js import的几种用法

    最近昨天公司小朋友离职,临时接收其负责的vue前端项目.vue好久没做了,很多东西都忘记或以前也没接触,几天开始慢慢写点vue的小知识,算是历程或备忘吧. import在js.ts中用了不知多少次,但 ...

  8. 基于 HTML5 WebGL + WebVR 的 3D 虚拟现实可视化培训系统

    前言 2019 年 VR, AR, XR, 5G, 工业互联网等名词频繁出现在我们的视野中,信息的分享与虚实的结合已经成为大势所趋,5G 是新一代信息通信技术升级的重要方向,工业互联网是制造业转型升级 ...

  9. vue 状态管理 四、Action用法

    系列导航 vue 状态管理 一.状态管理概念和基本结构 vue 状态管理 二.状态管理的基本使用 vue 状态管理 三.Mutations和Getters用法 vue 状态管理 四.Action用法 ...

  10. 微前端qiankun