规则引擎开发

原文:http://shellblog.sinaapp.com/?p=673

最近两个月以来,一直在开发公司的规则引擎系统,起初是想把引擎用到CRM系统中,后来经过多次讨论、多次变更,领导决定把这个规则引擎做成中间件,在平 台的高度来使用他。做成中间件,对规则引擎的要求更高了,这需要引擎具备高灵活性和伸缩性,来适应不同的业务系统。下面我来谈谈开发过程中我的一些经验。

曾经听板桥先生说过,架构设计需要从事物外部(通过与其他同类事物比较)和深入事物内部两种方式来进行,实际就是“做什么”和“怎么做”分离。首先,我需 要技术选型,在众多的规则引擎中,适合java项目的有ILog、Jess、Mandarax、Drools,由于ILog和Jess不是开源的,所以这 两个提前出局。剩下Mandarax和Drools,这两个开源项目各有千秋,但是我比较了一下,Mandarax最近几年都没有过更新,并且文档不全、社区不活跃,而Droos在google一搜,有很多的mail list供我参考,所以我很倾向于使用Drools。在一篇Mandarax的入门文档的评论里,我看到一个朋友在业务的角度来比较他们俩,看完后,我更加确定Drools是最适合我的。

接下来,我开始了解需求,初步规划规则引擎,在了解完各个系统的业务需求后,我大致定了下规则引擎要做的事情以及引擎与其他 系统的边界。由于各个系统之间差异比较大,要让引擎高可扩展,需要对需求进一步抽象,业务系统与规则引擎之间是通过模型来交互的,所以我们单独将模型抽象 出来,在引擎中可以对模型进行管理。另外一个需要抽象的地方就是规则的动作部分,由于每个系统数据库、业务的差异性,动作是不可能统一的,将动作抽象为动作类型,并可以单独管理,当有新系统接入引擎时,我们为其定义新的动作类型即可。

引擎有了初步规划后,我开始了解Drools,结合Drools的特点进一步规划引擎。Drools的官方文档上提供了很详细的例子来供我们入门,这里不再赘述入门级别的代码。我需要列举一下自己的引擎中需要实现的功能并在Drools中找到解决方案:

1.规则有效期

Drools中的date-effective和date-expires生来就是干这个的。

2.基于Cron的定时任务

Drools中的timer可以实现,并且官方例子很详细。

3.规则返回结果

Drools中的globle很适合。

4.规则优先级

可以使用salience来解决。

5.计算类的属性

对于一些需要预处理的属性,我们可以不定义字段,而是直接定义一个无参数的方法,具体可以参考这篇文章的4.8.3.1.1.1节。

6.规则的与或关系

这是Drools天生就支持的,并且是完美支持。

掌握了基本的技术点后,我开始设计规则的创建页面,整个规则引擎大致的思路是:把前端用户输入的语言(通俗易懂的白话语言)作为输入,经过一系列处理转换后,保存到数据库(我们特殊处理的语言)。在规则执行的时候,我们再从数据库拿出保存在数据库的“语言”,然后转换为Drools语言。只所以不把前端用户的输入直接转换为Drools语言,是因为我们的场景、属性、动作类型都是可以灵活变动的,如果创建之初就进行转换,会导致之后的扩展异常困难。

规则的创建页面包含规则的名称、有效期、条件信息和动作信息等,条件部分我们允许用户选择实体(模型)属性并可以对属性做四则运算或者与或关系,这样在保 证引擎的强大之时却不失简单之美。为了保证条件的正确性,系统需要在保存规则之前对用户输入的条件进行正确性检验。动作的创建首先是需要选择动作类型,然 后根据定义好的动作类型去输入相应的内容,这块还涉及到一些其他知识,比如静态变量和动态变量,这里不再展开细说。

说到页面了,那就接着来,由于是后台的管理界面,所以我毫不犹豫的选择了DWZ。选择DWZ有这几个考虑:一是我对他熟悉,上手快。二是他的速度还可以,并且对常用的浏览器都有较好的支持。关于DWZ的一些使用经验我都零零碎碎分享到了博客里,供以后参考。

上面说了,引擎的核心工作其实就是转换。我们需要把自己的语言转换为Drools语言,这里面就涉及到很多字符串的拼装,接下来我谈谈这部分工作的经验:

1.使用StringBuilder进行拼装,并且注意对拼装规则进行格式化(比如在每行后面换行,这样方便我们调试与排错)。

2.巧用正则表达式,比如我们要得到两个@之间的字符串,可以使用下面的代码:

1
2
3
4
5
Matcher m=Pattern.compile( "\\#(.*?)\\#" ).matcher(str);
         List<String> list = new ArrayList<String>();
         while (m.find()){
             list.add(m.group(1));
         }

3.以Drools规则语言的标准来组织程序,做到有条不紊。在代码关键部分多打log,方便调试。

完成引擎的解析工作后,剩下的部分就容易多了,无非是一些CRUD的工作,比如对场景的管理、场景中实体的管理、角色的管理等等。

引擎开发工作持续了将近一个月,接下来进入测试阶段。我们需要根据压测结果对引擎核心代码进行优化重构,这部分我主要借助VisualVM和 JConsole,在压测的同时使用VisualVM进行CPU采样,然后进行分析。经过几轮的测试,我发现之前自己的代码中有很多的不严谨之处,比如:

1.使用StatefulKnowledgeSession后没有调用dispose方法去释放内存资源,这一错误会让引擎不停的内存溢出。

2.没有对KnowledgeBase进行缓存,而是对字符串进行缓存。由于没有对KnowledgeBase进行缓存,每次请求到来之时都需要重新编译规则,而规则编译的成本相当高,没有对代码修正之前,给引擎300左右的并发CPU负载就到了10,而修正后,300的并发CPU负载没有超过1。

3.没有关闭Reader流,这个就太粗心了,幸好及时发现。

再剩下就是一些其他方面的优化,比如tomcat、mysql等,和主题无关,我留在心里。

猜你喜欢

转载自san-yun.iteye.com/blog/1884982