通用规则引擎——Rush(二)如何使用lua脚本编写规则

2023年 9月 29日 55.2k 0

前言

通用规则引擎虽然号称通用,但在处理一些特殊的业务时,仍有力所不逮的时候。因此,rush不仅支持表达式,还会支持lua,将来还会有更多引擎的支持。

Lua Runtime

LuaRuntime 是实现lua脚本的运行时,被单独放在lua_engine目录下。

LuaRuntime 同时实现了 RuleFlowAsyncRuleFlow 两种trait,也就是说他可以在同步和异步两种运行时中调用。并且标记这两个实现的feature为rule-flow,也就是说LuaRuntime可以独立的被使用,不需要和其他包耦合,此时flow会变成call。

先看看一下性能测试结果:

lua_async_flow       time:   [10.731 µs 10.847 µs 10.970 µs]

用法示例

快速开始

下面是直接使用 LuaRuntime 解析脚本的方式。

  • 需要在lua脚本的最后 需要返回检查结果【code,状态,0表示成功】 【message,附带消息】 【handle_function,执行逻辑的函数】,前面两个可缺省
const LUA_SCRIPT: &'static str = r#"
function handle(req)
    local resp = {}

    if req.source == "online" then
        resp.message = "线上渠道"
    elseif req.source == "offline" then
        resp.message = "线下渠道"
    else
        resp.message = "未知渠道:"..req.source
    end

    return resp
end
return {handle_function="handle"}
"#;

#[test]
fn test_lua_time() {
    let rt = LUA_SCRIPT.parse::().unwrap();

    let res: HashMap = rt
        .flow(r#"{"source":"online"}"#.parse::().unwrap())
        .unwrap();
    assert_eq!(res.get("message").unwrap().as_str(), "线上渠道");
}

规则和异步

  • rule LUA_RULE_SCRIPT _ lua 以规则的形式开头,并声明引擎为lua
  • lua_script: 第二行表示,下面的内容是一段脚本,直接解析使用即可
  • 可以在构建运行时,声明一些全局变量
  • LuaRuntime可以在异步中运行,但仍旧是以单线程的方式工作。详细可看尾语。
const LUA_RULE_SCRIPT: &'static str = r#"
rule LUA_RULE_SCRIPT _ lua
lua_script:
function handle(req)
    local resp = {}

    if req.source == ONLINE_CHANNEL then
        resp.message = "线上渠道"
    elseif req.source == OFFLINE_CHANNEL then
        resp.message = "线下渠道"
    else
        resp.message = "未知渠道:"..req.source
    end

    return resp
end

return {handle_function="handle"}
"#;

#[tokio::test]
async fn test_lua_rule_build() {
    let mut envs = HashMap::new();
    envs.insert("ONLINE_CHANNEL".into(), "online".into());
    envs.insert("OFFLINE_CHANNEL".into(), "offline".into());

    let rt = LuaRuntimeFactory::new()
        // .load(LUA_RULE_SCRIPT, envs)..unwrap(); //同步try_load
        .build(LUA_RULE_SCRIPT, envs)
        .await
        .unwrap();

    let res: HashMap = rt
        .async_flow(r#"{"source":"online"}"#.parse::().unwrap())
        .await
        .unwrap();
    assert_eq!(res.get("message").unwrap().as_str(), "线上渠道");
}

模块和文件

当然我们也可以从文件中加载代码。

入口文件 handle.lua

这里需要注意,引用了一个模块module

local md =  require("lua_script/module")

将文件加载并运行起来,这里需要指定通过文件加载lua_file: lua_script/handle.lua

const LUA_RULE_FILE: &'static str = r#"
rule LUA_RULE_FILE _ lua
lua_file: lua_script/handle.lua
"#;

#[derive(Deserialize)]
struct Resp {
    message: String,
}

#[test]
fn test_lua_from_file() {
    let rt = LuaRuntimeFactory::new()
        .load(LUA_RULE_FILE, HashMap::new())
        .unwrap();
    let resp: Resp = rt
        .flow(r#"{"hello":"world"}"#.parse::().unwrap())
        .unwrap();
    assert_eq!(resp.message.as_str(), "success")
}

自定义脚本解析

目前自带的脚本加载器只有lua_file:lua_script:

当然你可以自定义一些解析,先实现这个trait,未来可能有变

#[async_trait::async_trait]
pub trait AsyncCustomScriptLoad: Send + Sync {
    //在同步中尝试解析
    fn try_load(&self, _rule_name: String, _script: String) -> Option {
        None
    }
    async fn load(&self, rule_name: String, script: String) -> anyhow::Result;
}

然后将其注入到 LuaRuntimeFactory中,通过它的add_loader方法

尾语

为啥不实现http的脚本加载器?

远程加载脚本会带来很多安全问题,尤其是每个公司内部鉴权的方式都不太一样,所以类似http,ftp,oss之类的远程加载的方式就留使用者自己实现吧。

当然一个偷懒的做法是直接把云存储 挂载成本地目录,我就是这么干的,不过这需要处理应用间文件隔离的问题。

一个LuaRuntime可以在异步中工作,为啥执行任务还是单线程的?

这也是出于隔离和安全上考虑的,每个LuaRuntime对应一个线程,但多个LuaRuntime是并行的。

那可以用多个LuaRuntime增加性能吗?

当然是可以的,但前提是他们之间无法共享数据。比如一个lua脚本可以通过全局变量统计handle执行了多少次,但这在多运行时的情况下,这么做显然是有问题的。

对运行池的支持?

未来的计划是会支持的。主要是用来解决资源消耗问题。假设你有1w个规则脚本,那么会对应1w个线程,但他们中的大多数是不工作的,所以我们应该给工作繁重的规则多开资源,而闲下来的规则0资源。
这也解释了 为什么是LuaRuntimeFactory 而不是LuaRuntimeBuilder

真尾语

实现lua脚本当做规则执行并不复杂,但中间的思考,要怎样实现?为什么这么做?反而花去了大量时间。

最后呼唤小伙伴的参与

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论