本文最后更新于:June 30, 2023 pm
本文主要讲了轻量级规则引擎设计过程
通用轻量级规则引擎:RulEngine
关于
什么是规则引擎?本质上就是个数据选择器,每个规则只关心自己匹配到部分,来进行进一步的操作和处理,更像是一个管道操作,本质是上一次操作的输出作为下一次的输入,整个流程如图所示。
Input -> Rule1: Input data
Note over Rule1: RulEngine.Run(Rule1)
Rule1 -> Rule2 : next, NewInput
Note over Rule2: RulEngine.Run(Rule2)
Rule2 --> Rule1 : stop
Rule2 -> Rule3 : next, NewInput
Note over Rule3: RulEngine.Run(Rule3)
Rule3 -> Rule2 : stop
Rule3 -> RuleN : next, NewInput
Note over RuleN: RulEngine.Run(RuleN)
RuleN -> Rule3 : stop
RuleN -> Output : Output
技术
规则引擎核心部件采用 golang 作为主要开发语言来实现,规则用 JavaScript 脚本来动态实现,本质上是 golang 启动一个 JavaScript解释器,然后执行规则定义代码。
设计
生命周期
规则引擎生命周期如下所示:
Start-> InitInEnds: Initial Input Resources
InitInEnds-> InitOutEnds: Initial Output Resources
InitOutEnds-> LoadRules: Load Rules
LoadRules -> Working : Work
Working -> Working : Working State
Working-> Destory: Free Resources
Destory-> Stop: Stop Rule Engine
外部接口
- Start() : 启动规则引擎,返回值是个 MAP,里面包含了一些配置信息或者环境变量;
- InitInEnd() list.List: 初始化输入端的资源,通常指规则引擎需要监听的端;
- InitOutEnd() list.List: 初始化数据流出的目的地,比如数据库或者是转发;
- LoadRule() list.List: 加载规则,在这里指的是 JavaScript 脚本;
- Work(): 准备就绪开始工作;
- Destory(): 规则引擎停止之前的释放资源操作;
- Stop(): 停止规则引擎。
实现原理
RuleEngine 执行脚本过程:
data = InEnd.input
RuleEngine.work(inendId, data)
-> RuleEngine.call(Js1, Input): next, NewInput | stop
-> RuleEngine.call(Js2, Input): next, NewInput | stop
-> .......
-> RuleEngine.call(Jsn, Input): next, NewInput | stop
规则使用JavaScript来描述:
let name = "demo rule1";
let description = "ok, let's go";
let from = ["in_id1", "in_id2"];
let actions = [
function a(params) {
print("params1", params)
},
function b(params) {
print("params2", params);
}
// …………
];
function success() {
print("success callback rule call success");
}
function failed(error) {
print("failed callback ,rule call error:", error);
}
字段解释:
- name: 规则的名字
- description:描述信息
- from:来自哪个输入端,这是个数组,值是输入端的ID
- actions:规则过滤函数
- success:规则执行成功后回调
- failed:规则执行失败后回调
JavaScript 规则脚本回调函数:
// 其中params表示输入的数据
function ${name)(params) {
// todo
return ${RETURN};
}
- 回调函数不可以是匿名函数
- 回调函数必须是 1个参数
- 回调函数返回值必须是
"stop"
或者["next", params]
注意:规则描述里面所有字段必须是必填的,缺一不可。
重要表结构
规则引擎为了加速和利用硬件性能,所有运行时数据全部存在某个内存表里面,因此需要合理设计几张内存表。
输入端
字段名 | 类型 | 备注 |
---|---|---|
id | string | 唯一ID,系统分配 |
type | string | 类型 |
name | string | 名称 |
description | string | 描述文本 |
config | Map string | 配置信息,包含了输入端的有些配置,比如可能是MQTT的鉴权,Topic等 |
cts | timestamp | 创建时间 |
输入端一般指的是外部数据产生方,比如来自HTTP的请求,或者MQTT消息等。当创建一个输入端以后,系统会启动一个进程来维护这个输入端。
其中输入端业务必须实现接口:void RunEngine(data),最后数据流转的时候,回调这个接口:
inend.RunEngine(data){
id : = 自己的唯一ID
rulEngine.work(id, data)
}
规则
字段名 | 类型 | 备注 |
---|---|---|
id | string | 唯一ID,系统分配 |
in_end | string | 输入端的ID |
type | string | 类型 |
name | string | 名称 |
script | string | 规则脚本 |
description | string | 描述文本 |
config | Map string | 配置信息 |
cts | timestamp | 创建时间 |
规则表相对来说比较负责,除了基础字段,还包含了一个输入端ID。输入端ID主要用来标记该规则作用点,假设我们需要一个MQTT消息处理规则,则首先我们需要创建一个InEnd,然后创建一个规则,规则作用点为之前创建的InEnd。
输出端
字段名 | 类型 | 备注 |
---|---|---|
id | string | 唯一ID,系统分配 |
type | string | 类型 |
name | string | 名称 |
description | string | 描述文本 |
config | Map string | 配置信息 |
cts | timestamp | 创建时间 |
输出端和输入端结构保持一致,原理就是把数据最终吐出到某个地方。
系统用户表
字段名 | 类型 | 备注 |
---|---|---|
username | string | 用户名 |
password | string | 密码 |
string | ||
phone | string | 手机号码 |
extra_info | string | 扩展信息 |
因为用户管理不是本系统的主要功能,所以尽量做得简单,只做了个基础鉴权功能。如果觉得用户信息还需要扩展,那就在 extra 字段里面保存一些额外信息。
参考
[otto ] ( A JavaScript interpreter in Go)
[Rule engine] (OpenRules Rule Engine)
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!