作者:京东物流李振康锐刘斌王贝永义,规则引擎业务应用背景在业务逻辑中经常会出现一些冗长的判断,需要写很多ifelse,或者需要一些判断逻辑要经常修改。这部分逻辑如果用java代码实现,会面临代码量不可控,经常需要修改逻辑才能上线等诸多弊端。这时候我们就需要集成一个规则引擎来在线管理这些判断。2、规则引擎的选择目前开源的规则引擎有很多。根据原来的项目依赖和我们简单接触过的规则引擎,重点理解一下。几个流口水:-社区活跃,持续更新-使用广泛-复杂-学习成本高https://github.com/kiegroup/d...easy-rule:-简单易学-满足使用需求-不发布好久了新版https://github.com/j-easy/eas...easycode:-京东物流同事维护的一个平台-基于flowabledmn实现-简单直观的配置-海量系统使用总结:简单的配置规则可以访问easycode,平台提供配置页面,通过jsf交互。复杂规则需要动态生成规则,easycode暂不支持。从人气和活跃度上来说,drools要好于easy-rule,所以选择了drools。3.drools简单示例3.1引入依赖org.kiekie-spring${drools.version}3.2在drl文件中,我们写一个简单的demo规则:匹配一个sku对象00&&price<=100)then$p.setPoints(10);System.out.println("Rulenameis["+drools.getRule().getName()+"]");end//100点规则"100_points"when$p:Sku(price>100&&price<=1000)然后$p.setPoints(100);System.out.println("Rulenameis["+drools.getRule().getName()+"]");end//1000点规则"1000_points"when$p:Sku(价格>1000&&价格<=10000)然后$p.setPoints(1000);System.out.println("规则名称是["+drools.getRule().getName()+"]");end3.3使用起@TestpublicvoidtestOneSku(){Resourceresource=ResourceFactory.newClassPathResource("rules/skupoints.drl");KieHelperkieHelper=newKieHelper();kieHelper.addResource(资源);KieBasekieBase=kieHelper.build();KieSessionkieSession=kieBase.newKieSession();Skusku1=新的Sku();sku1.setPrice(10);kieSession.insert(sku1);intallRules=kieSession.fireAllRules();kieSession.dispose();System.out.println("sku1:"+JSONObject.toJSONString(sku1));System.out.println("所有规则:"+allRules);}@TestpublicvoidtestOneSku2(){Resourceresource=ResourceFactory.newClassPathResource("rules/skupoints.drl&qu哦;);KieHelperkieHelper=newKieHelper();kieHelper.addResource(资源);KieBasekieBase=kieHelper.build();StatelessKieSessionstatelessKieSession=kieBase.newStatelessKieSession();Skusku1=新的Sku(););statelessKieSession.execute(sku1);System.out.println("sku1:"+JSONObject.toJSONString(sku1));}3.4输出3.5总结如上,我们简单的使用了drools,只需要注意drl文件的语法是根据drl文件生成regularworkingmemory通过KieSession或者StatelessKieSession与workingmemory进行交互。整个过程并不复杂。请注意,KieHelper仅在演示中短暂使用。demo包括使用bean管理容器的方式。即使在简单的使用场景下,也不应该使用KieHelper来重复加载规则。但是,这不符合我们在线判断或频繁更改规则的需求。所以我们需要一种更高级的方式在实践中使用drools。4.Drools动态实践从上面的简单demo可以看出,规则依赖于drl文件的存在。在实际业务使用中,需要动态修改规则,不能直接使用drl文件。下面是我了解到的四种动态方案:drt文件,创建模板,动态生成drl文件,也是我们目前使用的方式。Excel文件导入其实类似于模板文件,还是不能直接交给业务人员使用。自己拼装String,动态生成drl文件。互联网上的大多数博客文章都是以非常原始的方式使用的。api方法,drools的api方法比较复杂,使用需要对drl文件有足够的了解。最后,下面介绍drools在项目中的实际使用。4.1配置规则我们的业务场景可以理解为多个缓冲池组成的网络结构。举个例子:上图中的每一个方块都是一个缓冲池,每一个连接都是一条从A缓冲池流向B缓冲池的规则。在实际场景中,有数百个缓冲池,其中大部分都有自己的规则,形成了一个复杂的网络。根据业务需求,缓冲池的流转规则需要经常变更。我们需要动态改变这些连接的条件或者改变业务中的连接。在这种情况下,如果使用静态drl文件来实现这些规则,需要数百个规则文件,维护量大,每次修改后使规则生效的成本也比较高。在这样的背景下,我们尝试drools的高层应用,也就是规则的动态实践。我们在创建缓冲池页面增加了流规则的创建。每个缓冲池维护自己的流规则,也就是自己的连接。如下图所示:4.2动态生成drldrt文件内容:(实际业务模板比这个复杂,还有一定的校验和业务逻辑,这里简化)/globalobjectglobaljava.util.Listlist;globaljava.util.ListstopIdList;globaljava.util.ListruleIdList;//导入的java类importcom.example.drools.bean.ClueModelimportorg.springframework.util.CollectionUtilsimportorg.apache.commons.lang3.StringUtils;importjava.lang.Longtemplate"CluePoolOut"//规则名称规则"clue_pool_@{cluePoolId}_@{id}"//参数标识当前规则是否不允许多次循环执行否-looptrue//参数优先级salience@{salience}//参数规则组这组规则只能有一个有效activation-group"out_@{cluePoolId}"//匹配LHSwhen$clue:ClueModel(cluePoolId==@{cluePoolId})ClueModel(CollectionUtils.isEmpty(@{sourceList})||源成员@{sourceList})ClueModel(CollectionUtils.isEmpty(@{cooperateTypeList})||cooperateTypememberOf@{cooperateTypeList})ClueModel(secondDepart==@{secondDepartmentId})ClueModel(regionNo==@{regionId})ClueModel(battleId==@{battleId})ClueModel(null!=estimateOrderCount&&(Long.valueOf(estimateOrderCount)@{amountCompareFlag}长。valueOf(@{amount})))//如果配置的RHS支持java语法则ruleIdList.add(@{id});$clue.setCluePoolId(Long.valueOf(@{outCluePoolId}));list.add(@{outCluePoolId});更新($线索);}endtemplate生成drl内容:根据队列和模板路径生成drl内容ListruleCenterVOS=newArrayList<>();CrmCluePoolDistributeRuleBusinessBattleVOvo=newCrmCluePoolDistributeRuleBusinessBattleVO();vo.setCooperateTypeList(Lists.newArrayList(1,2,4));vo.setAmountCompareFlag(">");vo.setAmount(100L);ruleCenterVOS.add(vo);Stringdrl=droolsManager.createDrlByTemplate(ruleCenterVOS,"rules/CluePoolOutRuleTemplate.drt");publicStringcreateDrlByTemplate(集合>objects,Stringpath){ObjectDataCompilercompiler=newObjectDataCompiler();尝试(InputStreamdis=ResourceFactory.newClassPathResource(path,this.getClass()).getInputStream()){returncompiler.compile(objects,dis);}catch(IOExceptione){log.error("创建drl文件失败!",e);}返回空值;}4.3加载drl在上面的简单例子中,我们使用KieHelper将规则文件加载到工作内存中。事实上,我们不可能在每次匹配时都重新加载所有的规则文件,所以我们可以将规则容器作为单例使用,通过以下方式或者使用@Bean等方式来管理容器。privatefinalKieServiceskieServices=KieServices.get();//kie文件系统需要缓存。如果每次都添加新规则,可能会出现问题。也就是说,之前添加到文件系统的规则没有了。privatefinalKieFileSystemkieFileSystem=kieServices.newKieFileSystem();//需要世界上唯一的privateKieContainerkieContainer;可以通过将内容写入kieFileSystem然后重新加载整个kieBase来重新加载规则,但是这种行为比较繁重和昂贵。也可以通过kieBase添加新文件来加载。成本很小,但是同步每个实例的成本比较高。KnowledgeBaseImplkieBase=(KnowledgeBaseImpl)kieContainer.getKieBase(kieBaseName);KnowledgeBuilderbuilder=KnowledgeBuilderFactory.newKnowledgeBuilder();Resourceresource=ResourceFactory.newReaderResource(newStringReader(ruleContent));builder.add(resource,ResourceType.DRL);if(builder.hasErrors()){thrownewRuntimeException("添加规则失败!"+builder.getErrors().toString());}kieBase.addPackages(builder.getKnowledgePackages());4.4匹配通过StatelessKieSession与规则引擎交互//获取一个链接StatelessKieSessionkieSession=droolsManager.getStatelessKieSession(RuleTemplateEnum.CLUE_POOL_OUT_RULE.getKieBaseName());//创建一个全局变量对象Listlist=newArrayList<>();ListstopIdList=Lists.newArrayList();Listresult=newArrayList<>();ListruleIdList=newArrayList<>();//插入全局变量kieSession.setGlobal("ruleIdList",ruleIdList);kieSession.setGlobal("list",列表);kieSession.setGlobal("stopIdList",stopIdList);kieSession.setGlobal("result",result);//执行规则kieSession.execute(clueModel);如果使用KieSession,则需要在使用kieSession.insert(clueModel);kieSession后关闭它。fireAllRules();kieSession.dispose();在执行规则的过程中,可以添加各种监听器来监听过程中的各种变化。空间的原因留给你去探索。五、小结从上面的过程中,我们体会到了动态规则的创建和使用。动态规则满足了我们对规则动态变化和规则统一管理的需求。我也总结了drools这种使用方式的几个优缺点。优点:方便的动态规则工作记忆匹配规则性能好几乎可以满足所有规则要求丰富完善的内置方法缺点:分布式一致性需要自己处理需要研发了解drl语法学习曲线陡峭匹配过程监控方式需要自己实现