引言:规则引擎的价值
在业务开发中,常遇到营销活动的折扣规则每周调整、风控系统反欺诈规则每日迭代、审批流程条件频繁变动等痛点。硬编码逻辑导致维护成本高、响应效率低。
规则引擎的核心价值是彻底解耦业务规则与系统代码,让频繁变动的业务规则无需开发介入,即可在线配置、动态生效,将规则迭代周期从'天级'压缩到'分钟级'。
一、规则引擎核心认知:基础概念与适用边界
1. 什么是 Java 规则引擎?
规则引擎是一种嵌入在应用程序中的组件,它将业务决策逻辑从业务代码中剥离出来,使用预定义的语义模块编写业务规则,通过接收数据输入、解释业务规则、根据规则做出业务决策,实现业务逻辑的灵活配置与快速迭代。
通俗来讲:规则引擎就是把「如果满足 XX 条件,就执行 XX 动作」的业务逻辑,从 Java 代码中抽离出来,交给专门的引擎管理,无需修改代码、重启服务,即可完成规则的更新与生效。
2. 核心适用场景
规则引擎并非银弹,只有在规则频繁变动的场景下才能发挥最大价值,核心适用场景包括:
- 风控反欺诈:交易风控、用户行为风控、反洗钱规则
- 营销活动:折扣规则、满减规则、会员权益规则、优惠券发放规则
- 审批流程:请假审批、报销审批、资质审核的条件判断
- 定价策略:商品定价、保费定价、服务费计算规则
- 合规校验:监管合规要求、数据校验规则、合同条款校验
3. 硬编码 VS 规则引擎 核心对比
| 对比维度 | 硬编码实现 | 规则引擎实现 |
|---|
| 规则与代码耦合度 | 极高,规则写死在业务代码中 | 极低,规则完全与业务代码解耦 |
| 规则迭代效率 | 极低,改规则需改代码、编译、打包、上线 | 极高,规则可在线配置、动态生效 |
| 业务人员参与度 | 完全无法参与,必须依赖开发人员 | 可通过可视化界面直接配置规则 |
| 规则可维护性 | 极差,规则散落在代码各处,难以排查 | 极好,规则统一管理、版本控制、可追溯 |
| 上线风险 | 改规则需全量回归,上线风险高 | 规则灰度发布,增量更新,风险可控 |
| 适用场景 | 规则固定不变,几乎不迭代的场景 | 规则频繁变动,需快速响应业务变化的场景 |
4. 易混淆概念明确区分
很多开发者会把规则引擎与工作流引擎、流程编排引擎混为一谈,三者的核心边界完全不同:
- 规则引擎:核心解决「WHAT」的问题,即满足什么条件,执行什么动作,聚焦于业务决策逻辑,无固定流程顺序
- 工作流引擎:核心解决「HOW」的问题,即业务按什么步骤、什么顺序流转,聚焦于流程的顺序、分支、回退、审批
- 流程编排引擎:核心解决「多个服务怎么组合执行」的问题,聚焦于分布式系统中多个服务的调用顺序、异常处理、降级熔断
二、主流 Java 规则引擎选型对比
本文所有选型均基于当前主流稳定版本,从性能、学习成本、功能完整性、社区活跃度四个维度进行对比,帮助选择适合业务场景的引擎。
| 引擎名称 | 最新稳定版 | 核心优势 | 核心劣势 | 适用场景 |
|---|
| Drools | 8.44.0.Final | 业界标杆,功能最全,支持 rete/phreak 算法,社区活跃,文档完善,支持动态规则热更新 | 学习成本高,有一定性能开销,轻量场景过重 | 企业级复杂规则场景,大量规则的风控、合规系统 |
| Easy Rules | 4.1.0 | 轻量级极简框架,API 简单易懂,学习成本极低,零依赖,支持注解和编程式两种方式 | 功能简单,不支持复杂的模式匹配,无规则管理体系 | 简单规则场景,小型项目的规则校验、简单业务决策 |
| LiteFlow | 2.11.1 | 国内开源,组件化规则编排,支持多种脚本语言,可视化界面完善,中文文档齐全,国内社区活跃 | 核心偏向流程编排,复杂规则模式匹配能力弱于 Drools | 国内企业级项目,规则编排、流程驱动的业务场景 |
| URule | 3.0.3 | 国产商业开源,全可视化规则配置,支持决策表、决策树,中文支持完善,适配国内企业需求 | 企业版收费,开源版功能有限,社区活跃度低于 Drools | 国内政企项目,需要可视化规则配置、无代码开发的场景 |
选型建议:
- 简单规则场景,快速上手:选 Easy Rules
- 企业级复杂规则场景,海量规则管理:选 Drools
- 国内项目,规则编排为主,需要中文文档:选 LiteFlow
- 政企项目,需要全可视化无代码配置:选 URule
三、底层原理深度拆解:通俗讲透规则引擎的核心逻辑
1. 规则引擎核心架构
规则引擎的核心架构分为 5 大核心模块,各模块职责清晰,协同完成规则的全生命周期管理,架构图如下:

- 规则管理中心:负责规则的新增、修改、版本管理、灰度发布、权限控制
- 规则解析器:负责将规则脚本编译解析成引擎可识别的内部结构,构建规则网络
- 规则执行引擎:核心模块,负责事实对象与规则的模式匹配,找出满足条件的规则
- 议程调度器:负责对满足条件的规则进行优先级排序,解决规则冲突,确定执行顺序
- 事实对象:规则引擎的输入数据,即业务数据,所有规则的条件判断都基于事实对象
2. 核心算法:Rete 算法通俗拆解
Rete 算法是由 Charles Forgy 在 1979 年提出的,是目前绝大多数规则引擎的核心算法,核心思想是用空间换时间,通过共享规则的条件节点,避免重复计算,大幅提升大量规则下的模式匹配效率。
举个通俗的例子:如果有 1000 条规则,每条规则都包含「订单金额>1000」这个条件,硬编码会对每条规则都执行一次这个判断,总共执行 1000 次;而 Rete 算法只会执行 1 次这个判断,把结果共享给所有用到这个条件的规则,极大减少重复计算。
Rete 网络核心节点与执行流程
Rete 算法会将所有规则拆解成节点,构建一个有向无环图(Rete 网络),核心节点与执行流程如下:

- 根节点:Rete 网络的入口,所有事实对象都会进入根节点
- 类型节点:过滤事实对象的类型,只保留规则需要的对象类型,比如只处理 OrderRiskFact 类型的对象
- Alpha 节点:单条件过滤节点,负责对事实对象的单个字段进行条件判断,比如「订单金额>1000」「用户等级==3」,每个 Alpha 节点对应一个独立的条件,相同条件会共享同一个 Alpha 节点
- Beta 节点:多条件关联节点,负责对多个事实对象的条件进行关联匹配,比如「用户订单金额>1000 且 用户历史订单数>5」,实现多条件的组合判断
- 终端节点:当所有条件都满足时,会进入终端节点,触发对应的规则,将规则加入议程调度器
- 议程调度:对所有触发的规则按优先级排序,解决规则冲突,按顺序执行规则
Phreak 算法:Rete 算法的企业级优化
Drools 6.x 之后引入了 Phreak 算法,针对 Rete 算法在事实更新时的性能瓶颈做了核心优化,也是目前 Drools 的默认算法,核心优化点:
- 延迟计算:只有当规则需要执行时,才进行模式匹配,避免不必要的计算
- 基于堆的议程调度:优化了冲突解决的效率,支持更灵活的优先级排序
- 节点内存优化:大幅减少了 Beta 节点的内存占用,降低了海量规则下的内存开销
- 并行匹配:支持多线程并行模式匹配,充分利用多核 CPU 的性能
3. 规则引擎完整执行生命周期
规则引擎的执行分为 5 个核心阶段,形成完整的闭环:
- 事实插入:将业务数据(事实对象)插入到规则会话中
- 模式匹配:规则引擎通过 Rete/Phreak 网络,将事实对象与所有规则进行匹配,找出所有满足条件的规则
- 冲突解决:对所有满足条件的规则,按优先级、生效时间等规则进行排序,确定执行顺序
- 规则执行:按顺序执行规则的动作逻辑,更新事实对象或执行业务操作
- 事实更新:规则执行后更新的事实对象,会重新进入模式匹配阶段,触发相关的规则(需避免死循环)
四、生产级实战:从零搭建可落地的规则引擎项目
实战代码基于 JDK 17 编写。
项目基础环境说明
- JDK 版本:JDK 17
- 项目管理:Maven
- 核心框架:Spring Boot 3.2.3
- 持久层框架:MyBatis Plus 3.5.6
- 数据库:MySQL 8.0
- 接口文档:Swagger3(springdoc 2.5.0)
- 规则引擎:Drools 8.44.0.Final、Easy Rules 4.1.0
- 工具类:Spring 工具类、Guava 33.1.0-jre、FastJSON2 2.0.52
实战一:轻量级规则引擎 Easy Rules 快速上手
Easy Rules 是极简的轻量级规则引擎,零依赖,API 简单易懂,适合简单规则场景,5 分钟即可完成开发。
1. Maven 核心依赖
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
2. 规则定义:订单折扣规则
package com.jam.demo.rule;
import lombok.extern.slf4j.Slf4j;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Slf4j
@Rule(name = "order_discount_rule", description = "订单金额折扣规则", priority = 1)
public class OrderDiscountRule {
@Condition
public boolean isDiscountAvailable(@Fact("orderAmount") BigDecimal orderAmount) {
return orderAmount.compareTo(new BigDecimal("1000")) >= 0;
}
@Action
public void applyDiscount(@Fact("orderAmount") BigDecimal orderAmount) {
BigDecimal discountAmount = orderAmount.multiply(new BigDecimal("0.9")).setScale(2, RoundingMode.HALF_UP);
log.info("订单金额{}元,满足折扣条件,折扣后金额:{}元", orderAmount, discountAmount);
}
}
3. 规则执行测试类
package com.jam.demo.test;
import com.jam.demo.rule.OrderDiscountRule;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import java.math.BigDecimal;
public class EasyRuleTest {
public static void main(String[] args) {
RulesEngine rulesEngine = new DefaultRulesEngine();
Rules rules = new Rules();
rules.register(new OrderDiscountRule());
Facts facts = new Facts();
facts.put("orderAmount", new BigDecimal("2000"));
rulesEngine.fire(rules, facts);
}
}
4. 执行结果
运行测试类,控制台输出如下,规则执行成功:
INFO com.jam.demo.rule.OrderDiscountRule - 订单金额 2000 元,满足折扣条件,折扣后金额:1800.00 元
实战二:企业级 Drools 8.x 生产级落地实战
Drools 是业界标杆的企业级规则引擎,本实战将从零搭建一个电商订单风控规则引擎,支持规则持久化、动态热更新、REST 接口调用,可直接用于生产环境。
1. 完整 Maven pom.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>rule-engine-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rule-engine-demo</name>
<description>Drools Rule Engine Demo</description>
<properties>
<>17
8.44.0.Final
3.5.6
2.5.0
2.0.52
33.1.0-jre
1.18.32
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-validation
org.springframework.boot
spring-boot-starter-jdbc
com.mysql
mysql-connector-j
runtime
com.baomidou
mybatis-plus-boot-starter
${mybatis-plus.version}
org.springdoc
springdoc-openapi-starter-webmvc-ui
${springdoc.version}
org.projectlombok
lombok
${lombok.version}
provided
com.alibaba.fastjson2
fastjson2
${fastjson2.version}
com.google.guava
guava
${guava.version}
org.drools
drools-bom
${drools.version}
pom
import
org.drools
drools-core
${drools.version}
org.drools
drools-compiler
${drools.version}
org.drools
drools-mvel
${drools.version}
org.kie
kie-api
${drools.version}
org.kie
kie-internal
${drools.version}
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. MySQL 数据库表结构(MySQL 8.0 可直接执行)
CREATE DATABASE IF NOT EXISTS rule_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE rule_db;
DROP TABLE IF EXISTS t_rule_info;
CREATE TABLE t_rule_info (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键 ID',
rule_key VARCHAR(64) NOT NULL COMMENT '规则唯一标识',
rule_name VARCHAR(128) NOT NULL COMMENT '规则名称',
rule_content TEXT NOT NULL COMMENT '规则内容(drl 脚本)',
rule_type VARCHAR(32) NOT NULL COMMENT '规则类型',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-禁用 1-启用',
version INT NOT NULL DEFAULT 1 COMMENT '规则版本',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(64) NOT NULL DEFAULT COMMENT ,
update_by () COMMENT ,
remark () COMMENT ,
(id),
KEY uk_rule_key_version (rule_key, version),
KEY idx_status (status)
) ENGINEInnoDB CHARSETutf8mb4 utf8mb4_unicode_ci COMMENT;
3. application.yml 配置文件
spring:
application:
name: rule-engine-demo
datasource:
url: jdbc:mysql://127.0.0.1:3306/rule_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.jam.demo.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
server:
port: 8080
4. 核心实体类与数据层
规则信息实体类 RuleInfo
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_rule_info")
@Schema(description = "规则信息实体")
public class RuleInfo {
@TableId(type = IdType.AUTO)
@Schema(description = "主键 ID", example = "1")
private Long id;
@Schema(description = "规则唯一标识", example = "order_risk_rule")
private String ruleKey;
@Schema(description = "规则名称", example = "订单风控规则")
private String ruleName;
@Schema(description = "规则内容(drl 脚本)", example = "rule \"xxx\" when then end")
private String ruleContent;
@Schema(description = "规则类型", example = "RISK")
private String ruleType;
@Schema(description = "状态:0-禁用 1-启用", example = "1")
private Integer status;
@Schema(description = "规则版本", example = "1")
private Integer version;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "创建人", example = "system")
private String createBy;
@Schema(description = "更新人", example = "system")
private String updateBy;
@Schema(description = "备注", example = "订单风控核心规则")
private String remark;
}
Mapper 接口 RuleInfoMapper
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.RuleInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RuleInfoMapper extends BaseMapper<RuleInfo> {
}
5. 规则管理服务层
服务接口 RuleInfoService
package com.jam.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.RuleInfo;
import java.util.List;
public interface RuleInfoService extends IService<RuleInfo> {
List<RuleInfo> listEnabledRules();
RuleInfo getLatestEnabledRuleByKey(String ruleKey);
boolean saveOrUpdateRule(RuleInfo ruleInfo);
}
服务实现类 RuleInfoServiceImpl
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.mapper.RuleInfoMapper;
import com.jam.demo.service.RuleInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.List;
@Slf4j
@Service
public class RuleInfoServiceImpl extends ServiceImpl<RuleInfoMapper, RuleInfo> implements RuleInfoService {
private final PlatformTransactionManager transactionManager;
public RuleInfoServiceImpl(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public List<RuleInfo> listEnabledRules() {
LambdaQueryWrapper<RuleInfo> queryWrapper = new LambdaQueryWrapper<RuleInfo>()
.eq(RuleInfo::getStatus, 1);
return this.list(queryWrapper);
}
@Override
public RuleInfo {
(!StringUtils.hasText(ruleKey)) {
log.warn();
;
}
LambdaQueryWrapper<RuleInfo> queryWrapper = <RuleInfo>()
.eq(RuleInfo::getRuleKey, ruleKey)
.eq(RuleInfo::getStatus, )
.orderByDesc(RuleInfo::getVersion)
.last();
.getOne(queryWrapper);
}
{
(ObjectUtils.isEmpty(ruleInfo)) {
log.warn();
;
}
(!StringUtils.hasText(ruleInfo.getRuleKey())) {
log.warn();
;
}
();
def.setName();
def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
transactionManager.getTransaction(def);
{
getLatestEnabledRuleByKey(ruleInfo.getRuleKey());
(!ObjectUtils.isEmpty(latestRule)) {
ruleInfo.setVersion(latestRule.getVersion() + );
latestRule.setStatus();
.updateById(latestRule);
} {
ruleInfo.setVersion();
}
.save(ruleInfo);
transactionManager.commit(status);
log.info(, ruleInfo.getRuleKey(), ruleInfo.getVersion());
result;
} (Exception e) {
transactionManager.rollback(status);
log.error(, e);
;
}
}
}
6. Drools 核心配置类(支持动态规则热更新)
package com.jam.demo.config;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.service.RuleInfoService;
import lombok.extern.slf4j.Slf4j;
import org.drools.compiler.compiler.DroolsParserException;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.drools.compiler.kie.builder.impl.KieFileSystemImpl;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.List;
@Slf4j
@Configuration
public class DroolsConfig {
private final RuleInfoService ruleInfoService;
public DroolsConfig(RuleInfoService ruleInfoService) {
this.ruleInfoService = ruleInfoService;
}
@Bean
public KieServices kieServices() {
return KieServices.Factory.get();
}
@Bean
public KieFileSystem kieFileSystem(KieServices kieServices) throws DroolsParserException, IOException {
KieFileSystem kieFileSystem ();
List<RuleInfo> ruleList = ruleInfoService.listEnabledRules();
(CollectionUtils.isEmpty(ruleList)) {
log.warn();
kieFileSystem;
}
(RuleInfo ruleInfo : ruleList) {
ruleInfo.getRuleKey();
ruleInfo.getRuleContent();
(!StringUtils.hasText(ruleContent)) {
log.warn(, ruleKey);
;
}
String.format(, ruleKey, ruleInfo.getVersion());
kieFileSystem.write(path, ruleContent);
log.info(, ruleKey, path);
}
kieFileSystem;
}
KieBuilder {
kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
kieBuilder.getResults();
(results.hasMessages(Message.Level.ERROR)) {
List<Message> errorMessages = results.getMessages(Message.Level.ERROR);
log.error(, errorMessages);
( + errorMessages);
}
log.info();
kieBuilder;
}
KieModule {
kieBuilder.getKieModule();
}
KieContainer {
kieServices.newKieContainer(kieModule.getReleaseId());
}
KieSession {
kieContainer.newKieSession();
log.info();
kieSession;
}
{
{
kieFileSystem(kieServices);
kieBuilder(kieServices, kieFileSystem);
kieModule(kieBuilder);
((KieContainerImpl) kieContainer).updateToKieModule((InternalKieModule) kieModule);
log.info();
} (Exception e) {
log.error(, e);
(, e);
}
}
}
7. 订单风控核心实现
风控事实对象 OrderRiskFact
package com.jam.demo.fact;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Schema(description = "订单风控事实对象")
public class OrderRiskFact {
@Schema(description = "订单号", example = "ORD202602280001")
private String orderNo;
@Schema(description = "用户 ID", example = "10001")
private Long userId;
@Schema(description = "用户等级:1-普通 2-VIP 3-超级 VIP", example = "1")
private Integer userLevel;
@Schema(description = "订单金额", example = "5000.00")
private BigDecimal orderAmount;
@Schema(description = "收货地址是否为常用地址", example = "true")
private Boolean isCommonAddress;
@Schema(description = "用户近 30 天订单数", example = "5")
private Integer orderCount30Days;
@Schema(description = "用户历史拒付次数", example = "0")
private Integer refusePayCount;
@Schema(description = "风控结果:PASS-放行 REVIEW-人工审核 REJECT-拦截", example = "PASS")
private String riskResult;
@Schema(description = "风控描述", example = "订单正常,放行")
private String riskDesc;
@Schema(description = "订单创建时间")
private LocalDateTime orderCreateTime;
}
风控规则文件 order_risk_rule.drl(resources/rules 目录下)
package com.jam.demo.rules;
dialect "mvel"
import com.jam.demo.fact.OrderRiskFact
global org.slf4j.Logger log;
/**
* 规则 1:超级 VIP 用户,订单金额小于 10000,直接放行
*/
rule "super_vip_pass_rule" salience 100
when $fact: OrderRiskFact(userLevel == 3, orderAmount < 10000, refusePayCount == 0)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("超级 VIP 用户,订单正常,直接放行");
log.info("订单{}触发超级 VIP 放行规则", $fact.getOrderNo());
end
/**
* 规则 2:VIP 用户,订单金额小于 5000,常用地址,直接放行
*/
rule "vip_pass_rule" salience 90
when $fact: OrderRiskFact(userLevel == 2, orderAmount < 5000, isCommonAddress == true, refusePayCount == 0)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("VIP 用户,订单正常,直接放行");
log.info("订单{}触发 VIP 放行规则", $fact.getOrderNo());
end
/**
* 规则 3:普通用户,订单金额大于 10000,直接拦截
*/
rule "normal_user_reject_rule" salience 80
when $fact: OrderRiskFact(userLevel == 1, orderAmount >= 10000)
then
$fact.setRiskResult("REJECT");
$fact.setRiskDesc("普通用户订单金额过高,系统自动拦截");
log.warn("订单{}触发高金额拦截规则", $fact.getOrderNo());
end
/**
* 规则 4:用户有历史拒付记录,直接拦截
*/
rule "refuse_pay_reject_rule" salience 1000
when $fact: OrderRiskFact(refusePayCount > 0)
then
$fact.setRiskResult("REJECT");
$fact.setRiskDesc("用户有历史拒付记录,系统自动拦截");
log.error("订单{}触发拒付记录拦截规则", $fact.getOrderNo());
end
/**
* 规则 5:非常用地址,近 30 天无订单,订单金额大于 2000,人工审核
*/
rule "uncommon_address_review_rule" salience 70
when $fact: OrderRiskFact(isCommonAddress == false, orderCount30Days == 0, orderAmount >= 2000)
then
$fact.setRiskResult("REVIEW");
$fact.setRiskDesc("非常用地址且无近期订单,需人工审核");
log.info("订单{}触发人工审核规则", $fact.getOrderNo());
end
/**
* 规则 6:默认规则,无匹配规则时放行
*/
rule "default_pass_rule" salience 0
when $fact: OrderRiskFact(riskResult == null)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("无匹配风控规则,默认放行");
log.info("订单{}触发默认放行规则", $fact.getOrderNo());
end
风控服务接口与实现类
package com.jam.demo.service;
import com.jam.demo.fact.OrderRiskFact;
public interface OrderRiskService {
OrderRiskFact executeRiskCheck(OrderRiskFact fact);
}
package com.jam.demo.service.impl;
import com.jam.demo.config.DroolsConfig;
import com.jam.demo.fact.OrderRiskFact;
import com.jam.demo.service.OrderRiskService;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
@Slf4j
@Service
public class OrderRiskServiceImpl implements OrderRiskService {
private final KieSession kieSession;
private final KieServices kieServices;
private final KieContainer kieContainer;
private final DroolsConfig droolsConfig;
public OrderRiskServiceImpl(KieSession kieSession, KieServices kieServices, KieContainer kieContainer, DroolsConfig droolsConfig) {
this.kieSession = kieSession;
this.kieServices = kieServices;
this.kieContainer = kieContainer;
this.droolsConfig = droolsConfig;
}
@Override
public OrderRiskFact executeRiskCheck(OrderRiskFact fact) {
if (ObjectUtils.isEmpty(fact)) {
log.warn("风控事实对象为空,无法执行规则");
return null;
}
try {
kieSession.setGlobal("log", log);
kieSession.insert(fact);
kieSession.fireAllRules();
log.info(, fact.getOrderNo(), ruleFiredCount);
fact;
} (Exception e) {
log.error(, fact.getOrderNo(), e);
(, e);
} {
kieSession.dispose();
}
}
{
droolsConfig.refreshRules(kieServices, kieContainer);
}
}
8. REST 接口层(Swagger3 支持)
订单风控控制器
package com.jam.demo.controller;
import com.jam.demo.fact.OrderRiskFact;
import com.jam.demo.service.OrderRiskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.util.ObjectUtils;
@Slf4j
@RestController
@RequestMapping("/api/risk")
@Tag(name = "订单风控接口", description = "订单风控规则执行相关接口")
public class OrderRiskController {
private final OrderRiskService orderRiskService;
public OrderRiskController(OrderRiskService orderRiskService) {
this.orderRiskService = orderRiskService;
}
@PostMapping("/check")
@Operation(summary = "执行订单风控校验", description = "传入订单信息,执行风控规则,返回风控结果")
public ResponseEntity<OrderRiskFact> executeRiskCheck(@RequestBody OrderRiskFact fact) {
if (ObjectUtils.isEmpty(fact)) {
return ResponseEntity.badRequest().body(null);
}
OrderRiskFact result = orderRiskService.executeRiskCheck(fact);
return ResponseEntity.ok(result);
}
}
规则管理控制器
package com.jam.demo.controller;
import com.jam.demo.config.DroolsConfig;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.service.RuleInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/rule")
@Tag(name = "规则管理接口", description = "规则信息管理、动态刷新相关接口")
public class RuleManageController {
private final RuleInfoService ruleInfoService;
private final DroolsConfig droolsConfig;
private final KieServices kieServices;
private final KieContainer kieContainer;
public RuleManageController(RuleInfoService ruleInfoService, DroolsConfig droolsConfig, KieServices kieServices, KieContainer kieContainer) {
this.ruleInfoService = ruleInfoService;
this.droolsConfig = droolsConfig;
this.kieServices = kieServices;
this.kieContainer = kieContainer;
}
@GetMapping("/list/enabled")
@Operation(summary = "查询所有启用的规则", description = "查询所有状态为启用的规则列表")
public ResponseEntity<List<RuleInfo>> {
List<RuleInfo> ruleList = ruleInfoService.listEnabledRules();
ResponseEntity.ok(ruleList);
}
ResponseEntity<RuleInfo> {
(!StringUtils.hasText(ruleKey)) {
ResponseEntity.badRequest().body();
}
ruleInfoService.getLatestEnabledRuleByKey(ruleKey);
ResponseEntity.ok(ruleInfo);
}
ResponseEntity<Boolean> {
(ObjectUtils.isEmpty(ruleInfo)) {
ResponseEntity.badRequest().body();
}
ruleInfoService.saveOrUpdateRule(ruleInfo);
ResponseEntity.ok(result);
}
ResponseEntity<Boolean> {
{
droolsConfig.refreshRules(kieServices, kieContainer);
ResponseEntity.ok();
} (Exception e) {
log.error(, e);
ResponseEntity.internalServerError().body();
}
}
}
9. 项目启动类
package com.jam.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.jam.demo.mapper")
public class RuleEngineDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RuleEngineDemoApplication.class, args);
}
}
10. 项目运行与测试
- 执行 MySQL 脚本,创建数据库和表
- 修改 application.yml 中的数据库连接信息
- 启动 Spring Boot 项目,项目启动成功后,访问 Swagger3 接口文档:http://127.0.0.1:8080/swagger-ui.html
- 调用
/api/risk/check 接口,传入以下测试参数,即可执行风控规则:
{
"orderNo": "ORD202602280001",
"userId": 10001,
"userLevel": 1,
"orderAmount": 20000,
"isCommonAddress": true,
"orderCount30Days": 5,
"refusePayCount": 0,
"orderCreateTime": "2026-02-28T12:00:00"
}
- 接口返回结果如下,规则执行成功:
{
"orderNo": "ORD202602280001",
"userId": 10001,
"userLevel": 1,
"orderAmount": 20000,
"isCommonAddress": true,
"orderCount30Days": 5,
"refusePayCount": 0,
"riskResult": "REJECT",
"riskDesc": "普通用户订单金额过高,系统自动拦截",
"orderCreateTime": "2026-02-28T12:00:00"
}
五、生产环境最佳实践与踩坑指南
1. 性能优化核心最佳实践
- 事实对象设计:只包含规则需要的字段,避免冗余字段,尽量使用不可变对象,减少更新触发的全量匹配
- 规则拆分优化:大规则拆分为小规则,按业务场景分类,避免一个规则包含过多条件导致 Rete 网络过于复杂
- 条件顺序优化:将过滤性强的条件放在规则前面,减少后续节点的计算量;相同条件的规则尽量共享节点
- 会话管理优化:使用短会话模式,每次规则执行后通过
dispose() 方法销毁会话,避免事实对象残留导致内存泄漏
- 规则预编译:静态规则启动时预编译,缓存 KieBase,避免每次执行都重新编译规则
- 并行匹配开启:海量规则场景下,开启 Drools 的多线程并行匹配,充分利用多核 CPU 性能
2. 生产环境高频踩坑与解决方案
| 常见坑 | 根因分析 | 解决方案 |
|---|
| 规则死循环 | 规则执行中更新事实对象,导致规则重新触发,无限循环 | 给规则添加 no-loop 或 lock-on-active 属性,避免同一规则重复触发 |
| 规则冲突 | 多个规则同时满足条件,优先级设置不合理,执行结果不符合预期 | 明确设置规则的 salience 优先级,核心规则优先级更高,避免同优先级规则 |
| 内存泄漏 | KieSession 未销毁,事实对象无法回收,内存持续上涨 | 使用 try-finally 结构,每次执行后必须调用 dispose() 方法销毁会话 |
| 动态规则加载线程安全问题 | 多线程同时刷新规则,导致规则执行异常 | 规则刷新添加分布式锁,保证刷新操作的原子性,刷新时暂停新的规则执行 |
| 规则语法错误导致容器崩溃 | 动态更新的规则存在语法错误,导致整个 KieContainer 初始化失败 | 规则保存前先做语法校验,隔离错误规则,只加载编译通过的规则 |
3. 规则治理企业级最佳实践
- 版本管理:所有规则必须有版本号,更新规则时新增版本,保留历史版本,支持一键回滚
- 灰度发布:新规则先对小流量用户生效,验证无误后再全量发布,避免全量故障
- 监控告警:监控规则执行时长、触发次数、异常率、匹配耗时,设置阈值告警,及时发现性能问题
- 权限管控:规则的新增、修改、发布必须经过审批,不同角色分配不同权限,避免误操作
- 自动化测试:每个规则必须配套对应的测试用例,规则更新前自动执行测试用例,保证规则逻辑正确
4. 高可用架构设计
- 集群部署:规则引擎服务集群部署,通过负载均衡分发请求,避免单点故障
- 规则同步:规则存储在数据库/配置中心,所有节点实时同步规则,保证集群内规则一致性
- 降级策略:规则引擎服务异常时,降级为默认规则执行,不影响核心业务流程
- 灾备方案:规则数据定期备份,支持跨机房灾备,避免规则数据丢失
六、高频问题答疑
1. 规则引擎什么时候该用,什么时候不该用?
该用的场景:规则频繁变动,需要快速响应业务变化;业务人员需要直接配置规则,无需开发介入;规则数量多,逻辑复杂,硬编码难以维护;需要统一管理规则,支持版本控制和审计。
不该用的场景:规则固定不变,几乎不迭代;规则逻辑极其简单,只有 1-2 个条件;性能要求极高(纳秒级响应),规则引擎有固定的性能开销;团队无规则引擎使用经验,学习成本过高。
2. 规则太多,性能下降怎么办?
- 优化 Rete 网络,共享条件节点,减少重复计算;
- 拆分规则集,不同业务场景使用独立的规则会话,避免一个会话加载所有规则;
- 对规则进行优先级分类,核心规则优先执行,提前终止不必要的匹配;
- 开启多线程并行匹配,充分利用多核 CPU 性能;
- 对不常用的规则进行懒加载,需要时再加载到会话中。
3. 动态规则热更新的核心实现逻辑是什么?
- 规则存储在数据库/配置中心,规则更新后触发刷新事件;
- 重新构建 KieFileSystem 和 KieModule,编译新的规则;
- 动态更新 KieContainer 中的规则模块,替换旧的规则;
- 刷新过程中保证线程安全,不影响正在执行的规则;
- 规则更新前必须做语法校验,避免错误规则导致容器崩溃。
4. 规则引擎和表达式引擎(Spring EL/QLExpress)的区别?
- 规则引擎:完整的规则管理、模式匹配、冲突解决、执行调度体系,适合大量复杂规则的企业级场景,支持规则可视化管理、版本控制、动态更新,学习成本较高,有一定性能开销。
- 表达式引擎:轻量级的表达式计算工具,适合简单的条件判断和数值计算,学习成本低,性能极高,但是没有规则管理、冲突解决、议程调度等能力,不适合大量复杂规则的场景。
- 核心总结:简单的条件计算用表达式引擎,复杂的业务规则管理用规则引擎。
5. 如何让业务人员无需写代码即可配置规则?
- 基于规则引擎开发可视化配置界面,提供表单化、拖拽式的规则配置能力;
- 预设通用规则模板,业务人员只需填写参数,无需编写代码;
- 提供规则语法校验和实时预览功能,配置后可立即验证规则效果;
- 结合自然语言处理,支持业务人员用自然语言描述规则,自动转换为规则脚本。
总结
规则引擎的核心价值,是将业务规则的控制权还给业务方,让技术团队专注于核心系统的架构设计,而不是陷入无休止的规则迭代中。规则引擎不是银弹,只有在合适的场景下才能发挥最大价值。在实际开发中,需要根据业务场景的复杂度、规则迭代频率、团队技术栈,选择合适的规则引擎,设计合理的规则架构,才能真正实现业务与技术的解耦,提升业务响应效率。