规则引擎clara-rules(代码片段)

willwillie willwillie     2023-03-21     612

关键词:

本文的主题是规则引擎,主要内容包括规则引擎的实现算法 rete算法,clojure开源的规则引擎clara-rules对规则的处理方式和特点,以及clojure edn文件格式处理等内容。
(在一段时间的思考之后,发现本文还是越写越乱,最后还是决定采用问答式来组织)

1.那么什么是规则引擎呢?

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

这个定义说的已经比较充分了,可见规则引擎是一个比较通俗易懂的概念。规则引擎就是将推理逻辑从业务代码分离,使得业务代码更加模块化的一个软件,接受一组规则和输入的事实,输出业务决策。

2.规则引擎是怎样工作的?

我们知道,当规则变得复杂后,事实变得庞大后,有很多东西就很难绕清楚了,这个时候就需要算法来帮忙。很多规则引擎使用的算法都是rete算法,那么rete算法的核心思想是什么呢?rete算法其实是一个模式匹配算法,规则引擎的核心是Pattern Matcher(模式匹配器)。不管是正向推理还是反向推理,首先要解决一个模式匹配的问题。(推理是数学证明中非常常见的一个名词),比如说正向推理:

正向推理又称为正向链接推理,其推理基础是逻辑演绎的推理链,它从一组表示事实的谓词或命题出发,使用一组推理规则,来证明目标谓词公式或命题是否成立。实现正向推理的一般策略是:先提供一批数据(事实)到总数据库中,系统利用这些事实与规则的前提匹配,触发匹配成功的规则(即启用规则),把其结论作为新的事实添加到总数据库中。继续上述过程,用更新过的总数据库中的所有事实再与规则库中另一条规则匹配,用其结论再修改总数据库的内容,直到没有可匹配的新规则,不再有新的事实加到总数据库为止。
  

3.模式匹配看起来其实挺简单的,一般的匹配例子有哪些?

对于规则的模式匹配,可以定义为: 一个规则是一组模式的集合。如果事实/假设的状态符合该规则的所有模式,则称该规则是可满足的。 模式匹配的任务就是将事实/假设的状态与规则库中的规则一一匹配,找到所有可满足的规则。

比如说,正则表达式就是一种模式匹配(学习正则表达式的成本很高,学正则表达式那么酸爽的过程应该不会忘记(不过如果能从推理的角度来看正则表达式,就溶图很多));

/<\\s*(\\S+)(\\s[^>]*)?>[\\s\\S]*<\\s*\\/\\1\\s*>/
匹配 HTML 标记。

\\s*匹配空白字符0次或多次;
(\\S+)匹配可连续非空白字符串,同时这个group就是后续被使用到的\\1;
(\\s[^>]*)?匹配一个以空白开始的不带”>”符号的字符串0次或者1次;这个是group2,\\2;

另外,正则表达式也是clojure中的一种非常基本的数据类型,clojure中的匹配re-find、re-matcher等函数也是提供了一个模式匹配的过程。

举另外一个例子来说明匹配的这个概念,这个例子是浏览器渲染,这里面将CSS规则树匹配到DOM树,最终生成渲染树的过程就是一个匹配的过程,说不定很多浏览器的原理就是采用规则引擎来做的(:)要是我来做这个方面的设计,说不定我就使用规则引擎来做了。)

4.那么模式是什么?

模式其实就是规则中最小的原子单位。

5.模式匹配看起来听简单的,但是觉得rete算法很难,rete算法是什么?rete算法有什么特别的地方?

rete算法也就是一个网络,将传入的对象在网络上传播。首先根据规则集建立一个网络(包括alpha节点和beta节点),然后将fact对象输入这个网络,看最终得到什么。

下面来看看规则引擎的算法rete,首先看一些概念
一些基本的概念
规则:一组规则的集合或者多组规则的集合
session:一组规则的集合,并且还可以往里面添加规则,移除规则,然后fire-rules(运行规则,如果一个规则被fired了,那么这个规则就是fired的状态了,不会再被fired了,除非有新的影响这个rule的fact加入或者移除。),以及查询。那么查询session能够查到什么呢?其实query就可以认为是正则表达式了,会查询满足一定规则的fact:当然还可以限定一些参数(暂时可以先不关心query,用处不大,而且可能会大致偏题)。
rete算法也就是说根据一定的规则建立一个网络,然后对于某一个用户,他可能有一个fact,你只要将这个fact放入这个网络生成的session中,并且通过fire-rules,就可以取得根据规则得到的结果;或者你也可以查询某个规则是否符合你的fact.

working memory:那么working memory到底是什么?我认为就是fact,可以认为每一个fact就是一个Java中的对象,对应相应的class();
rete网络:见下一个问题

6.在rete中,所有的rules组成了一张网络,这张网络是怎样组成的呢?

这张网络主要包括这些节点:

RootNode:Rete 网络的根节点,所有对象通过 Root 节点进入网络。

ObjectTypeNode:对象类型节点,保证所传入的对象只会进入自己类型所在的网络,提高了工作效率。

AlphaNode:Alpha 节点是规则的条件部分的一个模式,一个对象只有和本节点匹配成功后,才能继续向下传播。

JoinNode:用作连接(jion)操作的节点,相当于 and,相当于数据库的表连接操作,属于 betaNode
类型的节点。BetaNode 节点用于比较两个对象和它们的字段。两个对象可能是相同或不同的类型。我们将这两个输入称为左和右。BetaNode 的左输入通常是一组对象的数组。BetaNode 具有记忆功能。左边的输入被称为 BetaMemory,会记住所有到达过的语义。右边的输入成为 Alpha Memory,会记住所有到达过的对象。

这些概念再多也还是不够形象,下面结合一张经典的图片来说

这里的agenda是准备要选择一条规则来执行(可能会按照一定的规则);
这里的act rhs是指执行规则的右半部分。

7.上图形象的说明了rete算法,但是规则匹配的具体步骤是什么(可能会涉及到非常具体的一些东西)?你觉得这样合理不?

推理引擎在进行模式匹配时,先对事实进行断言,为每一个事实建立working memory,然后将WME(working memory)从RETE鉴别网络的根结点开始匹配,下面对alpha结点和beta结点处理WME的不同情况分开讨论。
(1)如果WME的类型和根节点的后继结点TypeNode(alpha结点的一种)所指定的类型相同,则会将该事实保存在该TypeNode结点对应的alpha存储区中,该WME被传到后继结点继续匹配,否则会放弃该WME的后续匹配;
(2)如果WME被传递到alpha结点,则会检测WME是否和该结点对应的模式相匹配,若匹配,则会将该事实保存在该alpha结点对应的存储区中,该WME被传递到后继结点继续匹配,否则会放弃该WME的后续匹配;
(3)如果WME被传递到beta结点的右端,则会加入到该beta结点的right存储区,并和left存储区中的Token进行匹配(匹配动作根据beta结点的类型进行,例如:join,projection,selection),匹配成功,则会将该WME加入到Token中,然后将Token传递到下一个结点,否则会放弃该WME的后续匹配;
(4)如果Token被传递到beta结点的左端,则会加入到该beta结点的left存储区,并和right存储区中的WME进行匹配(匹配动作根据beta结点的类型进行,例如:join,projection,selection),匹配成功,则该Token会封装匹配到的WME形成新的Token,传递到下一个结点,否则会放弃该Token的后续匹配;
(5)如果WME被传递到beta结点的左端,将WME封装成仅有一个WME元素的WME列表做为Token,然后按照(4)所示的方法进行匹配;
(6)如果Token传递到终结点,则和该根结点对应的规则被激活,建立相应的Activation,并存储到Agenda当中,等待激发。
(7)如果WME被传递到终结点,将WME封装成仅有一个WME元素的WME列表做为Token,然后按照(6)所示的方法进行匹配;

这样看起来是很合理的。但是我们似乎忽略了一个细节,那么就是下一个问题要回答的内容了。

8.rete网络是怎么建立起来的?是否可以结合代码说明?

mk-session的过程就是根据加入session中的所有规则,解释生成一个包括alpha和beta节点的rete网络。

(sc/defn mk-session*
  "Compile the rules into a rete network and return the given session."
  [productions :- #schema/Production
   options :- sc/Keyword sc/Any]
  (let [
        productions (with-meta (into (sorted-set-by production-load-order-comp) productions)
                      ;; Store the name of the custom comparator for durability.
                      :clara.rules.durability/comparator-name `production-load-order-comp)
        beta-graph (to-beta-graph productions)
        beta-tree (compile-beta-graph beta-graph)
        beta-root-ids (-> beta-graph :forward-edges (get 0)) ; 0 is the id of the virtual root node.
        beta-roots (vals (select-keys beta-tree beta-root-ids))
        alpha-nodes (compile-alpha-nodes (to-alpha-graph beta-graph))

        ;; The fact-type uses Clojure's type function unless overridden.
        fact-type-fn (or (get options :fact-type-fn)
                         type)

        ;; The ancestors for a logical type uses Clojure's ancestors function unless overridden.
        ancestors-fn (or (get options :ancestors-fn)
                         ancestors)

        ;; The default is to sort activations in descending order by their salience.
        activation-group-sort-fn (eng/options->activation-group-sort-fn options)

        ;; The returned salience will be a tuple of the form [rule-salience internal-salience],
        ;; where internal-salience is considered after the rule-salience and is assigned automatically by the compiler.
        activation-group-fn (eng/options->activation-group-fn options)

        rulebase (build-network beta-tree beta-roots alpha-nodes productions
                                fact-type-fn ancestors-fn activation-group-sort-fn activation-group-fn)

        get-alphas-fn (:get-alphas-fn rulebase)

        transport (LocalTransport.)]

    (eng/assemble :rulebase rulebase
                   :memory (eng/local-memory rulebase transport activation-group-sort-fn activation-group-fn get-alphas-fn)
                   :transport transport
                   :listeners (get options :listeners [])
                   :get-alphas-fn get-alphas-fn)))

具体过程不外乎循环,网络相关,不具体阐述。

9.clara-rules对规则处理的API有哪些特点?

一般调用api的步骤:
1.mk-session 根据规则创建session
2.在session中insert一些fact到session中。

inset “Inserts one or more facts into a working session. It does not
modify the given session, but returns a new session with the facts
added.”

通过上一部分我们已经知道了rete算法的原理。clara-rules也是采用rete算法实现的,在调用mk-session的时候就会生成一张rete网络,包括alpha节点和beta节点,并且将session中的fact放入到working memory中。这样一个session就生成好了,通过fire-rules就可以将里面的fact传入网络,使得网络最终运行到terminal节点的时候,再依次选择一条条规则来执行,最后执行规则右侧的rhs,因为网络是一个循环网络,直到结束为止。另外,defquery我认为query的过程就是加入一个规则(某些边和节点),然后查询满足了这个规则的session中的fact;也就是说query也会发起一次匹配,但是最终没有rhs需要去执行。

clara另外提供的一些api还有:
insert-all

“Inserts a sequence of facts into a working session

retract

“Retracts a fact from a working session”

fire-rules还有点特别:

“Fires are rules in the given session. Once a rule is fired, it is
labeled in a fired state and will not be re-fired unless facts
affecting the rule are added or retracted.

This function does not modify the given session to mark rules as
fired. Instead, it returns a new session in which the rules are
marked as fired.”

10.clojure edn文件

clojure.edn提供了两个处理edn文件的api:read 和read-string

那么什么是clojure的edn格式呢?全称是extensible data notation

edn is an extensible data notation. A superset of edn is used by Clojure to represent programs, and it is used by Datomic and other applications as a data transfer format.

也就是edn是可以代表clojure 程序代码的,也可以作为数据的传输格式。edn非常适合用于程序的交互。

那么一个能被clojure程序识别并使用的edn有什么特征呢?其实我觉得edn和json文件格式很像,只不过edn完全就是使用了clojure的语法

在edn文件中是没法直接识别clojure的正则表达式#""的;

# dispatch character
Tokens beginning with # are reserved. The character following # determines the behavior. The dispatches # (sets), #_ (discard), #alphabetic-char (tag) are defined below. # is not a delimiter.

也就是说在edn文件中,#只接受{ _ 以及数字和字符等。#"..."在edn中被认为是不正确的格式。

这个时候就需要用到re-pattern

(re-pattern s)
Returns an instance of java.util.regex.Pattern, for use, e.g. in
re-matcher.

那么什么是java.util.regex.Pattern呢?
这是java中表示正则表达式的一个类,clojure中正则表达式用到的其实就是这个类。例如:

user=> (re-pattern "\\\\d+")  
#"\\d+"  

这里\\前面也需要用到\\作为转义字符。

既然使用到了edn文件,当然希望可以热加载文件中的内容,但是就像前面经历的那样,一个session只能位于主线程中,没法在每次rpc调用的那个线程中再次读取并创建,那么这个时候设计上就要注意两点了:
1:session设计为动态变量
2.需要检测文件,如果edn文件内容发生改变,则通过一个函数去改变session的值。

11.规则的设计有什么要注意的?

规则的设计也是个技术活,当然要设计的简单易懂,扁平化;
尽量减少冲突;
我的一点想法:编程的时候好的做法其实可以是只创建一个session,因为不同的事实匹配的规则是不同的,这个一般来说都不会相互冲突的。对性能的影响一般来说也不大。

现在并没有写大量的规则,后续经验丰富了应该会有更多可以分享的。

flink规则引擎实践分享(代码片段)

Flink规则引擎实践分享文章目录Flink规则引擎实践分享一、实时规则引擎架构***二、规则抽象模型三、规则、条件查询封装**3.1规则封装3.2查询规则封装四、基于ClickHouse实现用户行为明细查询服务支持4.1ClickHouse从Kafka摄取数据4.2Cl... 查看详情

规则引擎easyrules介绍,应用及示例(代码片段)

浅析EasyRules规则引擎轻量级规则引擎EasyRulesJava规则引擎EasyRules一、EasyRules介绍1.1概述EasyRules是一个简单而强大的Java规则引擎,提供以下功能:轻量级框架和易于学习的API基于POJO的开发与注解的编程模型定义抽象的业务... 查看详情

.netrulesengine规则引擎使用(代码片段)

        规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业... 查看详情

业余草开源规则流引擎实践(代码片段)

在很多企业的IT业务系统中,经常会有大量的业务规则配置,而且随着企业管理者的决策变化,这些业务规则也会随之发生更改。为了适应这样的需求,我们的IT业务系统应该能快速且低成本的更新。适应这样的需求,一般的作... 查看详情

解决emqx4.3规则引擎获取消息中文乱码(代码片段)

解决EMQX4.3规则引擎获取消息中文乱码解决方案一、EMQX规则引擎配置二、订阅端代码1、订阅端代码2、订阅端回调代码三、发布端代码解决方案一、EMQX规则引擎配置该规则引擎为获取指定主题的所有信息,并转发到指定接口... 查看详情

golang规则引擎原理及实战(代码片段)

本文主要介绍规则引擎在golang中的使用,将首先介绍golang中主要的规则引擎框架,然后利用golang原生的parser搭建一个简单的规则引擎实现基本的bool表达式解析工作。 背景随着业务代码的不断迭代,诞生出了越来越多... 查看详情

golang规则引擎原理及实战(代码片段)

本文主要介绍规则引擎在golang中的使用,将首先介绍golang中主要的规则引擎框架,然后利用golang原生的parser搭建一个简单的规则引擎实现基本的bool表达式解析工作。 背景随着业务代码的不断迭代,诞生出了越来越多... 查看详情

规则引擎drools介绍使用及springboot整合drools(代码片段)

规则引擎Drools介绍、使用及SpringBoot整合Drools一、Drools介绍1.1优点1.2架构1.2.1Drools引擎的核心组件1.2.2规则引擎工作流程简述1.3Drools语法1.3.1规则文件1.3.1.1.规则详情1.属性详情2.条件部分-LHS3.结果部分-RHS1.3.1.2Drools关键词1.3.1.3Drools... 查看详情

规则引擎drools的使用(代码片段)

引入maven<dependency><groupId>org.drools</groupId><artifactId>drools-templates</artifactId><version>7.14.0.Final</version></dependency><dependency>& 查看详情

drools规则引擎入门指南(代码片段)

本篇博客主要讲解Drools常用的属性以及函数属性首先我们在resourcesules文件夹下创建一个Property.drl,还有一个DroolsApplicationPropertyTests1.salience优先级salience属性的值默认为0,它的值越大执行的优先级就越高,看如下代码在执行的时候... 查看详情

drools规则引擎环境搭建(代码片段)

一、关于drools规则引擎前面写过一篇Drools规则引擎相关的文章,这篇文章主要记录一下规则引擎的环境搭建和简单示例。不熟悉drools的朋友可以看看这篇文章:自己写个Drools文件语法检查工具——栈的应用之编译器检测语法错误... 查看详情

35activiti整合规则引擎drools(代码片段)

喜欢关注公众号:java乐园日常生活是由规则驱动的。红灯停绿灯行,这是我们的交通规则;我们站着往上跳,最终还是要落下来,这是地球的引力规则。规则在生活中无处不在。软件开发中我们也需要规则,满足什么规则应该... 查看详情

golang规则引擎原理及实战(代码片段)

本文主要介绍规则引擎在golang中的使用,将首先介绍golang中主要的规则引擎框架,然后利用golang原生的parser搭建一个简单的规则引擎实现基本的bool表达式解析工作。 背景随着业务代码的不断迭代,诞生出了越来越多... 查看详情

c#rulesengine规则引擎:从入门到看懵(代码片段)

说明RulesEngine是C#写的一个规则引擎类库,读者可以从这些地方了解它:仓库地址:https://github.com/microsoft/RulesEngine使用方法:https://microsoft.github.io/RulesEngine文档地址:https://github.com/microsoft/Ru 查看详情

pcb规则引擎之编辑器(语法着色,错误提示,代码格式化)(代码片段)

对于一个规则引擎中的脚本代码编辑器是非常关键的,因为UI控件直接使用对象是规则维护者,关系到用户体验,在选用脚本编辑器的功能时除了满足代码的编辑的基本编辑要求外,功能还需要包含;语法着色,错误提示,代码格式化,代... 查看详情

规则引擎drools示例:个人所得税计算器信用卡申请保险产品准入规则(代码片段)

...一、Drools实战1.1个人所得税计算器1.1.1名词解释1.1.2计算规则1.1.3实现步骤1.2信用卡申请1.2.1计算规则1.2.2实现步骤1.3保险产品准入规则1.3.1决策表1.3.2规则介绍1.3.3实现步骤一、Drools实战1.1个人所得税计算器本小节我们需要通过Drool... 查看详情

liteflow开源编排规则引擎(代码片段)

        osgi让java系统变成模块化的形式,ASM是一款修改字节码的框架,同类型的框架Cglib。这些框架能加载一个class信息,Javaagent&AttachAPI结合ASM        LiteFlow的理念很简单,就是把系统中的各个逻辑切... 查看详情

googleaviator——轻量级java表达式引擎实战(代码片段)

...擎技术及比较Drools简介Drools(JBossRules)是一个开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。除了应用了Rete核心... 查看详情