【大家的项目】通用规则引擎——Rush(一)可以自定义的规则引擎,告别发版,快速配置...

news2024/11/25 19:24:15

前言

很长一段时间在搞过增长和促销的事情,在实现各种活动和玩法时非常心累。每个新的玩法,都需要填一点代码,每次改动都需要走流水线发一次版,烦躁且痛苦。

对于这种,输入不确定,过程不确定,结果不确定的事情,搞个规则引擎处理这些问题往往比较高效。

要求:功能要足够强,配置要足够简单,性能要高,可以以多种方式接入。最重要是能够把活甩给运营。

规则引擎

规则引擎从功能上可以分为 通用规则引擎 和 业务规则引擎。顾名思义前者解决通用的问题,更灵活,难用。后者和具体的业务强绑定,但往往有个后台给用户点点点,更宜用。我们这里主要说通用规则引擎。

从规则编写上也可以分两种:解析表达式 语言脚本。前者使用表达式,相对简单(运营能接受的下限)。后者纯纯写代码,唯一的好处是,不用发版,热更新。

我们来看看比较有名的规则引擎:

基于表达式规则引擎表达能力性能其他
drools(java)编写难度高,与java强相关rete算法,顺序执行老牌规则引擎,反正我不用
gengine(基于golang和AST)自定义简单语法,与golang弱相关高,无论是规则间、还是规则内,都支持并发执行b站广泛使用,并在20年开源
Knetic/govaluate表达能力很强,函数和变量注入方便性能强力,benchmark有3.3的star,组装能力强
antonmedv/expr表达能力强性能强力,benchmark4.4的star,带一个表达式编辑器,谷歌优步字节等大厂在用

还有一些基于脚本的规则引擎,当然已经不是严格意义上的规则引擎,只要能够把脚本运行起来的都可以算是规则引擎。常见的 lua,tengo,甚至js和py都可以当做规则脚本运行起来。

Rush

github:https://github.com/woshihaoren4/rush

上面说了很多规则引擎,功能都很强大,为啥我们还要自己造一个,原因如下:

  • 需要支持多种规则格式,既可以用表达式,也可以用脚本,同时面向开发和运营。

  • 可扩展一定要强。我不满意那个模块都可以改造。

  • 性能要强,可并发,要安全。直接说答案,出现吧rust

Rush整体设计如下:

  • 一条规则分为when条件部分 和then执行部分。一个输入如果满足when,则结果根据then生成。

  • rush相当于一个容器,将这些计算,执行,函数组合在一起。

e285782a97adec2e80d3a4e793cf5e67.png

简单使用

//一个简单的规则
const SIMPLE_RULE: &'static str = "
rule COMPLEX_RULE
when
    age > 18
then
    stage = 'adult'
";
fn main(){
    //ExprEngine是一个表达式解析器,将规则表达式,解析为上图中的 Calc 和 Assgin
    //Rush是盛放规则的容器,它并不关心规则是如何解析和运行的,它只负责管理和调度
    let rh = Rush::from(Into::<ExprEngine>::into([SIMPLE_RULE]));
    // 执行一条规则
    let res:HashMap<String,String> = rh.flow(r#"{"age":19}"#.parse::<Value>().unwrap()).unwrap();
    
    assert_eq!(res.get("stage").unwrap().as_str(),"adult");

}

可以写普通函数那样将函数注入到rush里面

let rh = rh
        .register_function("abs", |i: i64| Ok(i.abs()));

数组自带了两个函数 contain:包含 sub:是否有子集,如下写法。

rule ARRAY_RULE
when
    contain([1,2,3,4],status);
    sub([2,3.1,'hello',true,2>>1],[1,'world']);
then
    message = 'success'

整个rush都可以通过抽象自己组装,如下自定义判断条件和生成器。当然你可以用表达式当条件,自定义生成。

struct CustomCalc;
impl CalcNode for CustomCalc{
    fn when(&self, _fs: Arc<dyn FunctionSet>, input: &Value) -> anyhow::Result<bool> {
        if let Value::String(s) = input{
            return Ok(s == "true")
        }
        return Ok(false)
    }
}
struct CustomExec;
impl Exec for CustomExec{
    fn execute(&self, _fs: Arc<dyn FunctionSet>, _input: &Value, output: &mut Value) -> anyhow::Result<()> {
        if let Value::Object(obj) = output{
            obj.insert("result".to_string(),Value::from("success"));
        }
        Ok(())
    }
}

#[test]
fn test_custom_calc_exec(){
    let rh = Rush::new()
        .register_rule("custom_rule",vec![CustomCalc],CustomExec);
        
    let res:HashMap<String,String> = rh.flow("true".parse::<String>().unwrap()).unwrap();
    assert_eq!(res.get("result").unwrap().as_str(),"success");

    let res:HashMap<String,String> = rh.flow("false".parse::<String>().unwrap()).unwrap();
    assert_eq!(res.get("result"),None);
}

当然因为我们这种拆分的设计,可以让规则并行计算。

let result = Into::<MultiRush>::into(rh)
    .multi_flow(r#"{"country":"China"}"#.parse::<Value>().unwrap())
    .await
    .unwrap();

更多例子参考

性能测试

目前并没有做什么优化,性能也非常强劲。 可以在将Rush git clone下来,在example目录下 cargo bench -- --verbose 测试一下

benchmark详情

我基于本地的环境做了一个benchmark测试,mac i7 六核 16g,从左到右[最小值, 平均值, 最大值]

assign_simple_parse  time:   [620.70 ns 625.08 ns 630.18 ns]
rule_full_parse      time:   [7.5513 µs 7.5794 µs 7.6094 µs]
multi_flow           time:   [15.363 µs 15.721 µs 16.184 µs]
sync_flow            time:   [2.9953 µs 3.0295 µs 3.0700 µs]
single_parse         time:   [165.08 ns 174.83 ns 186.49 ns]
simple_parse         time:   [2.6358 µs 2.6470 µs 2.6591 µs]
full_parse           time:   [19.868 µs 20.089 µs 20.356 µs]
have_function_rush   time:   [6.9074 µs 6.9507 µs 7.0011 µs]

表达式格式

  • 注意目前只实现了表达式解析,未来计划支持lua和wasm。欢迎有兴趣的小伙伴们参与进来。

关键字不允许做它用, when,then
rule [name] [description] [engine/default:expr] [...]
when
    [condition 1];
    [condition 2];
    ...
    [condition n];
then
    [key1 = execute 1];
    [key2 = execute 2];
    ...
    [keyn = execute n];

尾语

目前Rush还是一个较为初级的版本,后续api可能会有变动,但核心内容不会变,作者自己计划后续支持lua和wasm,非常欢迎有兴趣的小伙伴可以一起参与进来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1053588.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app:官方文档中的canvas实例剖析

效果 代码 <template><view><!-- 创建了一个宽度为300像素&#xff0c;高度为200像素的canvas元素。canvas-id属性被设置为"firstCanvas"&#xff0c;可以用来在JavaScript中获取该canvas元素的上下文对象。 --><canvas style"width: 30…

【python海洋专题三】图像修饰之画布和坐标轴

【python海洋专题三】图像修饰之画布和坐标轴 海洋与大气科学 上期读取nc水深文件&#xff0c;并出图 但是存在一些不完美&#xff0c;本期修饰 本期内容目录 1&#xff1a;改变画布大小 2&#xff1a;改变画布背景色 3&#xff1a;改变画布在显示屏中的显示位置 4&#xf…

建筑能源管理(3)——建筑能源监管

为了全面落实科学发展观&#xff0c;提高建筑能源管理水平&#xff0c;进一步降低能源和水资源消耗、合理利用资源&#xff0c;以政府办公建筑和大型公共建筑的运行节能管理为突破口&#xff0c;建立了既有政府办公建筑和大型公共建筑运行节能监管体系&#xff0c;旨在提高政府…

[python 刷题] 刷题常用函数

[python 刷题] 刷题常用函数 最近直到未来一段时间之内应该都会用 python 刷题了&#xff0c;所以记一下碰到的还蛮好用的方法 按照分区实现&#xff0c;基本上刷一个类型的题&#xff0c;了解一些常用函数&#xff0c;就更新一下 这里假设你对编程语言有一定的了解&#xf…

Python海洋专题七之Cartopy画地形水深图的陆地填充

Python海洋专题七之Cartopy画地形水深图的陆地填充 第一期图 本期&#xff1a; 上期 Cartopy画地形水深图 但是陆地没有填充 如图 本期内容 陆地填充 如图&#xff1a; 详细过程如下 1&#xff1a;陆地填充 land feature.NaturalEarthFeature(‘physical’, ‘land’…

如何在Qt6中引入Network模块

2023年10月1日&#xff0c;周日凌晨 如果用的是CMake find_package(Qt6 COMPONENTS Network REQUIRED) target_link_libraries(mytarget PRIVATE Qt6::Network) 但在实际使用中&#xff0c;我发现有以下几点要注意&#xff1a; 1、要把“mytarget”换成项目名称 2、要去掉…

指针巩固习题

1.该程序输出的结果为 #include<stdio.h> int main() {int arr[]{1,2,3,4,5};short *p(short*)arr;//转化类型int i0;for(i0;i<4;i){*(pi)0;//short型指针1跳过两个字节}for(i0;i<5;i){printf("%d",arr[i]);}return 0;} 2.程序的输出结果为 #include&…

oracle-使用PLSQL工具自行修改用户密码

1、使用PLSQL工具&#xff0c;输入用户名和原密码登录&#xff0c;如下图 2、登录后&#xff0c;在会话下拉菜单中找到”Change password..” 3、在跳出的窗口中配置新密码&#xff0c;修改完成后单击”确认”&#xff0c;后退出PLSQL 4、重新打开PLSQL&#xff0c;使用新密码登…

1. windows安装elasticSearch

目录 1. 下载 2. 安装和启动 1. 下载 下载地址&#xff1a;https://elasticsearch.cn/download/ 下载7.8.0版本&#xff1a; 下载后的elasticsearch-7.8.0-windows-x86_64.zip文件放在本地合适的位置。 2. 安装和启动 解压elasticsearch-7.8.0-windows-x86_64.zip文件&…

蜂蜜配送销售商城小程序的作用是什么

蜂蜜是农产品中重要的一个类目&#xff0c;其受众之广市场需求量大&#xff0c;但由于非人人必需品&#xff0c;因此传统线下门店经营也面临着痛点&#xff0c;线上入驻平台也有很多限制难以打造自有品牌&#xff0c;无法管理销售商品及会员、营销等&#xff0c;缺少自营渠道&a…

DirectX12_Windows_GameDevelop_1:向量代数

引言 向量在计算机图形学、碰撞检测和物理模拟中扮演者关键的角色。因此在游戏开发之前我们必须先了解向量。本章研究向量的使用。 一、向量 如果你对数学中的向量不太熟悉&#xff0c;建议阅读《3D数学基础&#xff1a;图形和游戏开发 第2版》&#xff0c;如果你需要某些PD…

敏感性分析一览

敏感性分析 SobolMorrisFourier Amplitude Sensitivity Test (FAST)Random Balance Designs - Fourier Amplitude Sensitivity Test (RBD-FAST)Delta Moment-Independent MeasureDerivative-based Global Sensitivity Measure (DGSM)Fractional Factorial Sensitivity Analysis…

Flutter笔记:滚动之-无限滚动与动态加载的实现(GetX简单状态管理版)

Flutter笔记 无限滚动与动态加载的实现&#xff08;GeX简单状态管理版&#xff09; 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq…

Spring Framework 学习笔记5:事务

Spring Framework 学习笔记5&#xff1a;事务 1.快速入门 1.1.准备工作 这里提供一个示例项目 transaction-demo&#xff0c;这个项目包含 Spring 框架、MyBatis 以及 JUnit。 对应的表结构见 bank.sql。 服务层有一个方法可以用于在不同的账户间进行转账&#xff1a; Se…

云原生Kubernetes:对外服务之 Ingress

目录 一、理论 1.Ingress 2.部署 nginx-ingress-controller(第一种方式) 3.部署 nginx-ingress-controller(第二种方式) 二、实验 1.部署 nginx-ingress-controller(第一种方式) 2.部署 nginx-ingress-controller(第二种方式) 三、问题 1.启动 nginx-ingress-controll…

Python海洋专题五之水深地形图海岸填充

Python海洋专题五之水深地形图海岸填充 海洋与大气科学 上期读取nc水深文件&#xff0c;并出图 但是存在一些不完美&#xff0c;本期修饰 本期内容 障眼法&#xff1a;把大于零的数据填充为陆地的灰色&#xff1b; 把等于零的数据画等深线为陆地和海洋的分界线&#xff01;…

怒刷LeetCode的第21天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;哈希表 方法二&#xff1a;计数器数组 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;分治法 方法二&#xff1a;快速幂 迭代 方法三&#xff1a;快速幂 递归 第三题 题目来源 题目内容 …

JUC第十二讲:JUC锁: 锁核心类AQS详解

JUC第十二讲&#xff1a;JUC锁: 锁核心类AQS详解 本文是JUC第十二讲&#xff0c;JUC锁: 锁核心类AQS详解。AbstractQueuedSynchronizer抽象类是核心&#xff0c;需要重点掌握。它提供了一个基于FIFO队列&#xff0c;可以用于构建锁或者其他相关同步装置的基础框架。 文章目录 J…

aarch64 平台 musl gcc 工具链手动编译方法

目标 手动编译一个 aarch64 平台的 musl gcc 工具链 musl libc 与 glibc、uclibc 等,都是 标准C 库, musl libc 是基于系统调用之上的 标准C 库,也就是用户态的 标准C 库。 musl libc 轻量、开源、免费,是一些 操作系统的选择,当前 Lite-OS 与 RT-Smart 等均采用自制的 mu…

【算法训练-贪心算法】一 买卖股票的最佳时机II

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【贪心算法】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…