前言
通用规则引擎虽然号称通用,但在处理一些特殊的业务时,仍有力所不逮的时候。因此,rush不仅支持表达式,还会支持lua,将来还会有更多引擎的支持。
Lua Runtime
LuaRuntime
是实现lua脚本的运行时,被单独放在lua_engine
目录下。
LuaRuntime
同时实现了 RuleFlow
和AsyncRuleFlow
两种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
以规则的形式开头,并声明引擎为lualua_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脚本当做规则执行并不复杂,但中间的思考,要怎样实现?为什么这么做?反而花去了大量时间。
最后呼唤小伙伴的参与