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


    前言

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

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

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

    规则引擎

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

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

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

    基于表达式规则引擎表达能力性能其他
    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

    简单使用

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

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

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

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

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

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

    1. struct CustomCalc;
    2. impl CalcNode for CustomCalc{
    3. fn when(&self, _fs: Arc, input: &Value) -> anyhow::Result<bool> {
    4. if let Value::String(s) = input{
    5. return Ok(s == "true")
    6. }
    7. return Ok(false)
    8. }
    9. }
    10. struct CustomExec;
    11. impl Exec for CustomExec{
    12. fn execute(&self, _fs: Arc, _input: &Value, output: &mut Value) -> anyhow::Result<()> {
    13. if let Value::Object(obj) = output{
    14. obj.insert("result".to_string(),Value::from("success"));
    15. }
    16. Ok(())
    17. }
    18. }
    19. #[test]
    20. fn test_custom_calc_exec(){
    21. let rh = Rush::new()
    22. .register_rule("custom_rule",vec![CustomCalc],CustomExec);
    23. let res:HashMap = rh.flow("true".parse::().unwrap()).unwrap();
    24. assert_eq!(res.get("result").unwrap().as_str(),"success");
    25. let res:HashMap = rh.flow("false".parse::().unwrap()).unwrap();
    26. assert_eq!(res.get("result"),None);
    27. }

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

    1. let result = Into::::into(rh)
    2. .multi_flow(r#"{"country":"China"}"#.parse::().unwrap())
    3. .await
    4. .unwrap();

    更多例子参考

    性能测试

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

    benchmark详情

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

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

    表达式格式

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

    1. 关键字不允许做它用, when,then
    2. rule [name] [description] [engine/default:expr] [...]
    3. when
    4. [condition 1];
    5. [condition 2];
    6. ...
    7. [condition n];
    8. then
    9. [key1 = execute 1];
    10. [key2 = execute 2];
    11. ...
    12. [keyn = execute n];

    尾语

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

  • 相关阅读:
    自己总结的wireshark抓包技巧
    手机没电用日语怎么说?你会吗?柯桥常用日语学习
    最新持续更新Crack:LightningChart 行业使用大全
    HALCON:Programming With HALCON/.NET
    谷歌新大楼正式开放:“龙鳞”顶棚、100%电动、最大地热桩系统······
    大三程序员实习面试经历(Java)
    LeetCode回溯算法组合问题——216.组合总和III
    orcale 单表查询和多表联合查询
    Snipaste 截图悬浮工具【实用教程】
    【英语】常见连音规则
  • 原文地址:https://blog.csdn.net/u012067469/article/details/133445889