当前位置: 首页 > 后端技术 > Java

我们自研的Ice规则引擎是开源的

时间:2023-04-01 23:42:33 Java

前言背景介绍规则/流程引擎想必大家都很熟悉。比较熟悉的有Drools、Esper、Activiti、Flowable等,很多大公司也热衷于研究自己的规则引擎。它们都是用来解决灵活场景下的复杂规则和流程问题。如果你想改变配置,你可以生成/生效新规则,摆脱硬编码的苦海。毕竟在现有的基础上修改配置和编排规则/流程的成本比硬编码要低很多,但是使用市面上现有的规则引擎来编排,一来接入成本和学习成本都不低,二来,随着时间的推移,规则变得越来越复杂,有些场景不适用,这让人们的抱怨越来越多。【设计思路】为了便于理解,设计思路会附上一个简单的充值实例。【例】X公司将在国庆长假期间开展为期7天的小额充值活动。活动内容如下:活动时间:(10.1-10.7)活动内容:充值100元送5元余额(10.1-10.7)充值50元送10积分活动备注(10.5-10.7):不叠加(充值100元只能获得5元余额,不会叠加10积分)。让我们简单地分解一下。要完成这个活动,我们需要开发以下模块:发现要下发密钥。这把钥匙从哪里来?如图所示,当用户充值成功后,会生成对应充值场景的参数包Pack(如Activiti/DroolsFact),该参数包中会包含充值用户的uid。充值金额spend、充值时间requestTime等信息。我们可以通过定义的key(类似于map.get(key))获取包中的值。模块的设计没有任何问题。关键是后面怎么排,实现配置自由。接下来,我们将通过上述现有的节点,来说明不同规则引擎在核心编排上的优缺点,并对比ice是如何做的。《流程图实现》类Activiti和Flowable实现流程图实现,应该是我们最常想到的编排方式吧~看起来非常简单易懂。通过特殊的设计,比如去掉一些不必要的线条,可以把UI做的更简洁。但是因为时间属性,时间其实是一个规则条件,加上之后就变成了:好看。“执行树实现”Drools实现(WhenXThenY)看起来不错,用时间线试试:还是比较简单的,至少比起流程图,我会更愿意修改这个。“变”以上两种方案的优势在于可以将一些分散的配置和业务很好的管理在一起。配置的小改动很容易实现,但在实际业务场景中,你可能还是会被锤。灵活变化,一切都不一样了。“理想”不会变,别着急,就这样,上线吧。《现实》①把100元充值改成80元,10分改成20分,时间到10.8结束参与热情不高,去掉不叠加送,全部送(稍微想一下,就是花几个脑细胞动一下就好了,总比改完代码再上线好吧!)③5元的余额不能发太多,设置100的库存。顺便说一句,如果库存不够,充值100元还是要给10分。更离谱的是,流程图和执行树实现的主要缺点是牵一发而动全身,换一个节点就需要有远见和远见。比如实际的活动内容要比这复杂的多,时间线也有很多。考虑到这一点,加上使用学习框架的成本,得不偿失。最后发现比hardcoding好。该怎么办?“冰是怎么做到的?”""引入关系节点""关系节点控制业务流程[AND]所有子节点中,如果有一个返回false,则该节点也为false,全部为true为true。为false的地方,终止执行,类似Java的&&[ANY]如果所有子节点有一个返回true,则该节点也为true,全部为false,则为false,终止执行当执行到true时,类似于Java的||[ALL]所有子节点都会被执行,如果有任何一个返回true,则该节点也为true,如果没有一个为true且有一个节点为false,则为false,如果没有true或false,则返回none,并且所有的子节点执行完都会终止[NONE]所有的子节点都会执行,不管子节点返回什么,都不会返回none[TRUE]所有的子节点都会执行,不管子节点返回什么,会返回true,没有子节点会返回true(其他没有子节点会返回none)「引入叶子节点」叶子节点就是实际处理的节点[Flow]一些条件和规则节点,比如例子中的ScoreFlow[Result]一些结果节点,比如例子中的AmountResult和PointResult[None]一些不干扰流程的动作,比如组装工作等等,下面会介绍TimeChangeNone,有了上面的节点,我们怎么组装它?如图,使用树结构(镜像旋转传统树),执行顺序还是类似中序遍历,从根开始执行,根是关系节点,从上到下依次执行子节点.如果用户充值金额为70元,执行流程:此时可以看到可以将之前需要剥离出来的时间整合到各个节点中,将时间配置返回给节点。如果没有到执行时间,比如发出积分的节点会在10.5天后生效,那么在10.5之前,可以理解为这个节点不存在。【变更与问题解决】对于①直接修改节点配置,对于②直接将根节点的ANY改为ALL(叠加投放和非叠加投放的逻辑都在这个节点上,属于这个节点的逻辑应该由这个节点来解决)对于③由于库存不足,相当于不发给用户,那么AmountResult返回false,流程继续往下执行,没有任何变化,又增加了一个棘手的问题。当时间线复杂时,测试如何工作并测试并发?10.1开始的活动必须在10.1之前开发上线。比如9.15怎么测试10.1开始的事件?在ice中,只需要稍微修改一下:如图,引入一个负责改变时间的节点TimeChangeNone(改变包中的requestTime),后续节点的执行取决于包中的时间。TimeChangeNone类似于一个变时插件一样,如果测试是并行的,那么在每个人负责多个测试的业务中添加一个变时插件。为什么要这样拆解“特征”?为什么这些变化和问题可以这样解决?其实就是利用树结构解耦,流程图和执行树来实现,在改变逻辑的时候,难免要向前看和向后看,但是ice不需要。ice的业务逻辑都在这个节点上,每个节点可以代表一个单独的节点。逻辑,比如我把不叠加发送的逻辑改成叠加发送,就只限于ANY节点的逻辑,只要改成我想要的逻辑即可,至于子节点,不要特别注意。相互依赖的包流,每个节点执行的后续流程不需要自己指定。因为执行后的执行过程不再是自己控制的,所以可以复用:如图,这里使用的TimeChangeNone参与活动,如果还有一个H5页面需要展示,不同的展示也跟时间有关,怎么办?你只需要在presentationactivity中使用同一个TimeChangeNone实例,如果其中一个发生变化,另一个就会更新,避免了到处都在改变时间的问题。同样,如果线上出现问题,比如sendAmount接口挂了,不会因为报错返回false继续执行,而是提供一个可选的策略,比如把Pack和执行的节点放到盘,等到接口修复好了,再继续冰上运行(因为放置的时间就是问题发生的时间,不用担心事后修复的问题结束不会生效)。希望运行,但是没有头像,所以可以选择跳过错误继续。此处对下单规则进行了详细说明。同样的原则也可以用于模拟。你只需要在Pack中添加需要mock的数据,就可以运行了。在上面“引入前向节点”的逻辑中,我们可以看到存在一些AND节点的紧绑定关系。为了简化视图和配置,增加了转发节点的概念。当且仅当前向节点的执行结果不为false时,只有该节点相连时才会执行该节点,且其语义与AND连接的两个节点一致。专业文档codeTalk很便宜。给我看代码...github:https://github.com/zjn-zjn/ice