# 规则引擎
规则引擎实现了将业务决策从应用程序代码中分离出来,接受数据输入,解释业务规则,并根据业务规则做出业务决策。规则引擎其实就是一个输入输出平台。
它将复杂多变、经常需要调整的业务规则(例如:如果用户是VIP且订单金额超过500元,那么自动免运费)从固定的程序代码中剥离出来,单独进行管理。这样一来,当业务策略需要变更时(比如把免运费门槛提高到800元),业务人员无需找程序员修改代码和重新部署系统,只需在规则引擎中简单调整规则即可立即生效。
# 常见的规则引擎
通过界面配置的成熟规则引擎
# 这种规则引擎相对来说就比较重,但是功能全,也有部分业务会选择这个,比较出名的有:Drools、URule
# Drools 中文网 : http://www.drools.org.cn
# URule 官网 : http://www.bstek.com
基于JVM脚本语言
# 这种其实不是一个成熟的规则引擎,应该算是规则引擎中的核心技术,因为Drools这种相对太重了
# 很多公司会基于一些jvm的脚本语言,开发轻量级的规则引擎,如:Groovy、AviatorScript、QLExpress
# Groovy 官网 : http://www.groovy-lang.org
# AviatorScript 文档 : https://www.yuque.com/boyan-avfmj/aviatorscript
# QLExpress GitHub : https://github.com/alibaba/QLExpress
基于java代码的规则引擎
# 上面是基于jvm脚本语言去做的,会有一些语法学习的成本,所以就有基于java代码去做的规则引擎
# 比如通过一些注解实现抽象的方式去做到规则的扩展,比较出名的有: Easy Rules
# Easy Rules GitHub : https://github.com/j-easy/easy-rules
# jvs-rules : 可视化的业务规则设计器 https://gitee.com/software-minister/jvs-rules
# AviatorScript
# AviatorScript 编译和执行的入口是 AviatorEvaluatorInstance 类
# 该类的一个实例就是一个编译和执行的单元,这个单元我们称为一个 AviatorScript 引擎
# 你可以多个引擎,每个引擎可以设置不同的编译和运行选项
# AviatorEvaluator.getInstance() 返回一个全局共享的 AviatorScript 引擎
# 编译脚本文件
- 在你的 java 项目里引用下 AviatorScript 的依赖
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.1</version>
</dependency>
- 编写你的第一个 AviatorScript 脚本,AviatorScript 脚本源码文件约定以 .av 作为文件后缀
## av/hello.av 两个 # 号引入一行注释
println("hello, AviatorScript!");
- 编写一个类来运行测试脚本
package com.example.springbootdemo.test;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.Expression;
public class AviatorScriptDemo {
public static void main(String[] args) throws Exception {
// 使用 AviatorEvaluatorInstance#compileScript 方法编译脚本到 Expression 对象
Expression exp = AviatorEvaluator.getInstance().compileScript("av/hello.av");
// 调用 Expression#execute() 方法执行
exp.execute();
}
}
# Drools
Drools 核心组件:
# KIE(Knowledge Is Everything):统一知识管理容器
# DRL(Drools Rule Language):规则描述语言
# Guvnor(Workbench):可视化规则设计器
# Phreak 算法:新一代高效规则匹配引擎
# Spring Boot 集成实战
- 依赖配置
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-spring-boot-starter</artifactId>
<version>7.73.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.73.0.Final</version>
</dependency>
- 规则引擎配置类
@Configuration
public class DroolsConfig {
private static final String RULES_PATH = "rules/";
// 创建 Drools 的文件系统 Bean,用于管理规则文件
@Bean
public KieFileSystem kieFileSystem() throws IOException {
// 获取 KieServices 实例并创建新的内存文件系统
KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
Files.list(Paths.get(RULES_PATH)).forEach(path -> {
// 将每个规则文件写入到 KieFileSystem 中
kieFileSystem.write(ResourceFactory.newFileResource(path.toFile()));
});
return kieFileSystem;
}
// Drools 的核心容器,包含所有编译后的规则
@Bean
public KieContainer kieContainer() throws IOException {
KieServices kieServices = getKieServices();
KieRepository kieRepository = kieServices.getRepository(); // 规则仓库
// 使用文件系统中的规则创建构建器
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem()).buildAll();
// 获取编译过程中的所有消息(包括错误、警告)
kieBuilder.getResults().getMessages().forEach(m ->
System.out.println("规则编译: " + m)
);
// 创建并返回 KieContainer,使用默认的发布 ID
return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
}
// 实际执行规则的会话
@Bean
public KieSession kieSession() throws IOException {
// 从 KieContainer 创建新的规则会话并返回
return kieContainer().newKieSession();
}
}
// 这段配置代码完成了 Drools 规则引擎的完整初始化流程:
// 1、扫描 rules/ 目录下的规则文件
// 2、将规则文件加载到内存文件系统
// 3、编译规则
// 4、创建规则容器
// 5、提供规则会话供业务代码使用
# 可视化规则设计(Workbench)
- 部署 Drools Workbench
# 访问 http://localhost:8080/business-central (admin/admin)
docker run -p 8080:8080 -p 8001:8001 -d --name drools-workbench jboss/drools-workbench-showcase:7.73.0.Final
- 创建决策表
| 条件 | 动作 |
|---|---|
| 会员等级=VIP | 折扣率=0.8 |
| 购物车金额>1000 | 折扣率=0.85 |
| 活动期间 && 新用户 | 折扣率=0.7 |
- 规则版本管理
// 获取特定版本规则
KieContainer container = kieServices.newKieContainer(
kieServices.newReleaseId("com.example", "discount-rules", "1.2.0")
);
# 核心源码解析
- 规则匹配引擎(Phreak算法)
public class PhreakRuleEngine implements RuleEngine {
public void fireRules(WorkingMemory wm) {
// 1. 创建规则网络
RuleNetwork network = buildRuleNetwork(rules);
// 2. 事实对象传播
for (Object fact : wm.getFacts()) {
network.insert(fact);
}
// 3. 执行匹配(惰性求值)
List<Activation> activations = network.match();
// 4. 按优先级执行
activations.sort(Comparator.reverseOrder());
for (Activation act : activations) {
act.execute();
}
}
}
- Spring集成适配器
public class KieAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public KieContainer kieContainer() {
return KieServices.Factory.get().getKieClasspathContainer();
}
@Bean
@ConditionalOnMissingBean
public KieSession kieSession(KieContainer kieContainer) {
return kieContainer.newKieSession();
}
@Bean
public DroolsRuleEngine droolsRuleEngine(KieSession kieSession) {
return new DefaultDroolsRuleEngine(kieSession);
}
}
- 规则热更新机制
@RestController
public class RuleUpdateController {
@Autowired
private KieContainer kieContainer;
@PostMapping("/reload-rules")
public void reloadRules() {
// 触发规则重新加载
kieContainer.updateToVersion(
kieContainer.getReleaseId().getVersion()
);
}
}
# 企业级最佳实践
- 规则版本灰度发布
public class RuleVersionRouter {
@Value("${rule.version.weights}")
private Map<String, Double> versionWeights;
public KieSession getVersionedSession() {
String version = selectVersionByWeight();
return kieContainer.newKieSession(version);
}
private String selectVersionByWeight() {
double rand = Math.random();
double cumulative = 0.0;
for (Map.Entry<String, Double> entry : versionWeights.entrySet()) {
cumulative += entry.getValue();
if (rand <= cumulative) {
return entry.getKey();
}
}
return "default";
}
}
- 规则执行监控
public class RuleExecutionListener extends DefaultAgendaEventListener {
@Override
public void afterMatchFired(AfterMatchFiredEvent event) {
Rule rule = event.getMatch().getRule();
long duration = System.currentTimeMillis() - startTime;
// 记录规则执行日志
RuleLog log = new RuleLog(
rule.getName(),
duration,
event.getMatch().getObjects()
);
// 发送到监控系统
metricsService.recordRuleExecution(log);
}
}
// 注册监听器
kieSession.addEventListener(new RuleExecutionListener());
- 性能优化策略
// 启用无状态会话(无会话缓存)
StatelessKieSession session = kieContainer.newStatelessKieSession();
// 批量执行优化
List<Object> facts = Arrays.asList(order1, order2, order3);
session.execute(facts); // 一次引擎调用处理所有事实
// 规则分区执行
@Bean
public KieBase kieBase() {
KieBaseConfiguration config = KieServices.Factory.get().newKieBaseConfiguration();
config.setOption(PartitionEvaluationOption.PARTITIONED); // 启用分区
return kieContainer.newKieBase(config);
}
# 实战:电商促销系统开发
- 领域模型设计
public class PromotionContext {
private User user;
private Order order;
private List<Product> products;
private double finalDiscount = 1.0; // 默认无折扣
private List<String> appliedRules = new ArrayList<>();
}
public class User {
private String level; // VIP, NORMAL
private int points;
private boolean isNew;
}
- 规则服务层
@Service
public class PromotionService {
@Autowired
private KieSession kieSession;
public PromotionContext applyPromotions(PromotionContext context) {
// 插入事实对象
kieSession.insert(context.getUser());
kieSession.insert(context.getOrder());
context.getProducts().forEach(kieSession::insert);
// 全局变量设置
kieSession.setGlobal("logger", LoggerFactory.getLogger(getClass()));
// 执行规则
kieSession.insert(context);
kieSession.fireAllRules();
return context;
}
}
- 可视化规则设计(DRL示例)
rule "NewUserWelcomeDiscount"
when
$user : User(isNew == true)
$ctx : PromotionContext(user == $user)
then
$ctx.setFinalDiscount(0.8);
$ctx.addAppliedRule("新用户首单8折");
end
rule "VIPBirthdayDiscount"
when
$user : User(level == "VIP", birthday == today())
$ctx : PromotionContext(user == $user)
then
$ctx.setFinalDiscount(0.7);
$ctx.addAppliedRule("VIP生日特惠7折");
end
# 生产环境避坑指南
- 规则冲突解决
// 显式设置规则优先级
rule "HighPriorityRule"
salience 100 // 优先级数值越高越先执行
when ... then ...
end
- 循环规则防护
// 设置最大规则触发次数
KieSessionConfiguration config = KieServices.Factory.get()
.newKieSessionConfiguration();
config.setProperty("drools.maxRuleExecutions", "10000");
- 内存泄漏预防
try (KieSession session = kieContainer.newKieSession()) {
// 使用try-with-resources自动释放资源
session.insert(fact);
session.fireAllRules();
}