规则引擎核心概念:规则的定义、优先级、匹配模式与热更新

做市商系统里,规则引擎就像大脑里的「决策中枢」。你想想看,每秒成百上千的订单进来,哪些该接、哪些该拒、哪些要路由到不同交易所——全靠规则引擎来拍板。今天我就把规则引擎的几个核心概念掰开揉碎了讲清楚。

一、规则的定义:不只是「如果-那么」

很多人以为规则就是简单的 if-else。其实在量化系统里,规则是一个结构化的决策单元。我习惯把规则拆成三部分:

  • 条件(Condition):触发规则的前提,比如「订单金额 > 100万 USDT」
  • 动作(Action):条件满足时执行的操作,比如「拒绝订单并告警」
  • 元数据(Metadata):规则的名称、版本、生效时间、创建人等

举个例子,一条典型的风控规则长这样:

{
  "ruleId": "RISK_001",
  "name": "单笔订单金额上限",
  "condition": "order.amount > 1_000_000",
  "action": "REJECT + ALERT",
  "priority": 10,
  "status": "ACTIVE",
  "version": 2
}

这里有个细节——条件表达式怎么写。我建议用 DSL(领域特定语言)而不是硬编码。我在项目中遇到过,早期用 Java 代码写规则,每次改规则都要重新编译部署,太痛苦了。后来换成 Groovy 脚本或者 MVEL 表达式,改规则就像改配置文件一样简单。

我的习惯:规则的条件表达式尽量保持「无状态」。不要依赖外部变量,所有输入都通过上下文对象传递。这样规则可以随意组合、测试,不会出现「这个规则在 A 场景能用,在 B 场景就崩了」的尴尬。

三、规则的优先级:谁先说话?

当多条规则同时匹配时,谁先执行?这就涉及到优先级了。我见过不少系统因为优先级没设计好,导致「白名单规则被黑名单规则覆盖」的惨案。

优先级通常用数字表示,数字越小优先级越高。我个人习惯这样划分:

优先级范围 用途 示例
1-10 系统级规则(不可绕过) 账户冻结、交易所熔断
11-50 风控规则 金额上限、频率限制
51-100 业务规则 路由选择、费率优化
101+ 默认规则 兜底策略

为什么要这样分层?因为系统级规则必须优先执行。我曾经遇到过一个 bug:某条业务规则的优先级设成了 5,结果它先于「账户冻结」规则执行了。用户账户明明被冻结了,业务规则还傻乎乎地去路由订单——嗯,那笔订单直接飞出去了,还好被交易所风控拦住了。

避坑指南:优先级不要用「0」作为最高优先级。因为很多数据库或配置中心的默认值就是 0,容易产生歧义。我一般从 1 开始。

三、规则的匹配模式:白名单 vs 黑名单

匹配模式说白了就是「允许谁」和「拒绝谁」。这两个模式看似简单,但用错了会出大问题。

白名单模式

白名单的意思是:只有名单里的才允许通过。比如「只有 VIP 账户才能使用高频交易策略」。白名单的优点是安全可控,缺点是维护成本高——每加一个新用户都要改规则。

黑名单模式

黑名单的意思是:名单里的拒绝,其他都放行。比如「禁止 IP 段 192.168.1.x 访问交易接口」。黑名单的优点是灵活,但风险也大——万一漏了一条黑名单规则,坏人就进来了。

我个人的经验是:风控场景用白名单,业务场景用黑名单。举个例子:

  • 风控规则:只有白名单里的交易所才能交易(防止误连钓鱼交易所)
  • 业务规则:黑名单里的币种不能做市(其他币种都可以)

你可能会问:「能不能混用?」当然可以。我做过一个系统,规则引擎支持同时配置白名单和黑名单,逻辑是:先检查黑名单,再检查白名单。如果既不在黑名单也不在白名单,就走默认策略。

核心原则:白名单是「默认拒绝,特例允许」;黑名单是「默认允许,特例拒绝」。两者结合使用时,一定要明确优先级和默认行为。

四、规则的动态加载与热更新

做市商系统是 7x24 小时运行的,你不能说「我要改一条规则,先停个机」。所以热更新是必备能力。

热更新的核心就三个字:不停机。具体怎么做?我分享一个我常用的方案:

  1. 规则存储:规则存在数据库或配置中心(比如 ZooKeeper、etcd)
  2. 规则缓存:引擎启动时加载到本地内存,用 ConcurrentHashMap 或 Guava Cache 存着
  3. 监听变更:监听配置中心的变更事件,或者定时轮询数据库(比如每 5 秒检查一次版本号)
  4. 原子切换:用读写锁或 CopyOnWrite 机制,保证规则切换时不会出现「半条规则」的情况

代码示例(伪代码):

public class RuleEngine {
    private volatile Map<String, Rule> rules = new ConcurrentHashMap<>();
    
    public void refreshRules() {
        // 从数据库加载最新规则
        List<Rule> newRules = loadRulesFromDB();
        // 原子替换
        this.rules = newRules.stream()
            .collect(Collectors.toConcurrentMap(Rule::getId, r -> r));
    }
    
    public RuleResult evaluate(Order order) {
        // 遍历规则,按优先级执行
        for (Rule rule : rules.values().stream()
                .sorted(Comparator.comparingInt(Rule::getPriority))
                .collect(Collectors.toList())) {
            if (rule.matches(order)) {
                return rule.execute(order);
            }
        }
        return RuleResult.PASS;
    }
}

这里有个坑:规则热更新时,正在执行的订单怎么办?我建议用「版本号」机制。每条规则带一个版本号,订单进来时记录当前规则版本。如果规则更新了,正在执行的订单继续用旧版本,新订单用新版本。这样就不会出现「同一个订单被新旧规则各判一次」的混乱。

我的经验:热更新不要做得太频繁。我见过一个团队每 10 秒就刷新一次规则,结果 CPU 全花在规则加载上了。建议设置最小更新间隔,比如 30 秒。另外,更新前最好做一次「规则校验」,防止语法错误导致引擎崩溃。

五、知识体系总览

说了这么多,我画了一张图帮你梳理一下规则引擎的核心逻辑:

规则引擎核心概念总览 规则定义 • 条件(Condition) • 动作(Action) • 元数据(Metadata) • DSL表达式 规则优先级 • 系统级(1-10) • 风控级(11-50) • 业务级(51-100) • 默认级(101+) 匹配模式 • 白名单 (默认拒绝) • 黑名单 (默认允许) 动态加载与热更新 • 配置中心监听 → 本地缓存更新 • 原子切换 + 版本号机制 → 不停机 规则引擎 = 定义 + 优先级 + 匹配模式 + 热更新

这张图把四个核心概念串起来了。你看,规则定义是基础,优先级决定执行顺序,匹配模式决定「谁进谁出」,热更新保证系统持续运行。四者缺一不可。

好了,规则引擎的核心概念就讲到这里。记住一句话:规则是死的,但引擎是活的。设计时多想想「如果规则变了怎么办」,你的系统会健壮很多。


公众号:蓝海资料掘金营,微信deep3321