一、规则引擎架构设计:整体架构分层、规则库设计、规则解析器、规则执行引擎、决策表与决策树
大家好,我是你们的老朋友。今天咱们聊聊规则引擎的架构设计。说实话,这块内容我琢磨了好几年,踩过不少坑,也积累了一些心得。你想想看,一个规则引擎如果架构没搭好,后面改起来那叫一个痛苦。我见过太多项目,规则一多就跑不动,或者改个规则要重启系统——嗯,这些都是架构设计时没想清楚。
1.1 整体架构分层
先说说整体分层。我个人习惯把规则引擎分成四层,每一层各司其职,互不干扰。这样做的好处是,哪一层出了问题,改哪一层就行,不会牵一发动全身。
核心四层架构:
- 接入层:负责接收外部请求,做参数校验和预处理
- 规则解析层:把文本规则变成内存中的对象结构
- 规则执行层:真正跑规则逻辑的地方
- 数据持久层:存规则、存日志、存结果
我在项目中遇到过一种情况:团队把规则解析和规则执行混在一起写。刚开始觉得挺方便,后来规则量到了几百条,每次改个解析逻辑都要重新部署执行引擎,搞得大家都不敢动代码。所以,分层这件事,说白了就是给自己留后路。
1.2 规则库设计
规则库怎么设计?这问题我回答过不下十次。说白了,规则库就是规则的「家」。这个家如果乱糟糟的,找条规则比找钥匙还难。
我建议用关系型数据库来存规则元数据,用文件系统或对象存储来存规则内容。为什么这么搞?因为规则元数据经常要查(比如查某个规则属于哪个场景、谁创建的、什么时候生效),而规则内容一般是一次写入多次读取。
规则库表结构示例:
-- 规则主表
CREATE TABLE rule_definition (
rule_id VARCHAR(32) PRIMARY KEY,
rule_name VARCHAR(128) NOT NULL,
rule_content TEXT NOT NULL, -- 规则内容(JSON或DSL)
rule_version INT DEFAULT 1,
status TINYINT DEFAULT 0, -- 0:禁用 1:启用
scene_code VARCHAR(64), -- 所属场景
priority INT DEFAULT 0, -- 优先级
create_time DATETIME,
update_time DATETIME
);
-- 规则日志表
CREATE TABLE rule_execution_log (
log_id BIGINT AUTO_INCREMENT PRIMARY KEY,
rule_id VARCHAR(32),
request_id VARCHAR(64),
input_params JSON,
output_result JSON,
execute_time INT, -- 执行耗时(ms)
create_time DATETIME
);
我曾经犯过一个错误:把规则内容和元数据全塞在一个字段里。结果后来要按场景查询规则,不得不全表扫描,那叫一个酸爽。所以,该拆的表一定要拆,别偷懒。
1.3 规则解析器
规则解析器,就是把人类写的规则文本,变成机器能理解的数据结构。你想想看,如果规则写的是 age > 18 AND score >= 60,解析器得把它拆成「条件A:age > 18」和「条件B:score >= 60」,还得知道它们之间是「与」关系。
我个人习惯用抽象语法树(AST)来表示解析后的规则。每个节点代表一个操作符或操作数,树的结构天然支持嵌套和优先级。
解析器核心流程:
- 词法分析:把规则文本拆成 token 序列(比如关键字、操作符、变量名)
- 语法分析:根据语法规则,把 token 序列组装成 AST
- 语义校验:检查变量是否存在、类型是否匹配、有没有循环引用
- 优化转换:把 AST 转换成更适合执行的中间表示
嗯,这里要注意一点:解析器一定要做缓存。我见过一个项目,每次请求都重新解析规则,结果 CPU 全耗在解析上了。其实规则一旦发布,短时间内不会变,解析一次缓存起来就好。
1.4 规则执行引擎
执行引擎是规则引擎的心脏。它的任务很简单:拿到解析好的规则结构,配上输入参数,算出结果。但简单的事往往最难做好。
我建议把执行引擎设计成「无状态」的。什么意思?就是引擎本身不保存任何中间状态,每次执行都是独立的。这样做的好处是:可以水平扩展,一台机器扛不住了,再加一台就行。
避坑指南:我曾经把规则执行结果缓存到了引擎的内存里,结果多节点部署时,A节点和B节点的缓存不一致,导致同一个请求在不同节点上得到不同结果。后来我改用 Redis 做统一缓存,才解决了这个问题。
执行引擎的另一个关键点是「短路优化」。比如一个规则是 A AND B AND C,如果 A 已经为 false,那 B 和 C 就不用算了。别小看这个优化,规则多了以后,性能差距能到几十倍。
1.5 决策表与决策树
决策表和决策树,是规则引擎里两种常用的「高级玩法」。它们解决的是同一个问题:当规则条件组合特别多的时候,怎么让规则更清晰、更高效。
决策表,说白了就是一个二维表格。行是条件组合,列是动作结果。我一般在规则条件不超过 5 个、每个条件的取值不超过 3 个时用决策表。再多了,表格就变成「天书」了。
| 年龄 | 收入(万) | 信用分 | 决策结果 |
|---|---|---|---|
| < 25 | < 10 | < 600 | 拒绝 |
| < 25 | ≥ 10 | ≥ 600 | 人工审核 |
| ≥ 25 | ≥ 10 | ≥ 600 | 通过 |
| ≥ 25 | < 10 | ≥ 600 | 人工审核 |
决策树就不一样了。它是树形结构,每个节点是一个条件判断,根据判断结果走向不同的分支。决策树的优势在于:它天然支持「先判断最重要的条件」,这样可以快速过滤掉大部分不满足的情况。
我在项目中遇到过一种场景:风控规则有 20 多个条件,如果用决策表,表格得有 2^20 行,根本没法维护。后来改成决策树,先按「是否黑名单」判断,再按「年龄」判断,最后按「收入」判断,效率直接翻倍。
选型建议:
- 条件少(≤5个)、取值少 → 决策表,直观好维护
- 条件多、有优先级 → 决策树,性能更好
- 条件之间有依赖关系 → 决策树,天然支持
好了,这一章的内容就到这里。规则引擎的架构设计,说白了就是「分而治之」——把大问题拆成小问题,每层只关心自己的事。下一章咱们聊聊规则语言的设计,那可是规则引擎的「灵魂」所在。
公众号:蓝海资料掘金营,微信deep3321