规则集拆分策略:大规则集拆分为小规则集、规则分组与分层、规则优先级设计

规则引擎跑得慢,十有八九是规则集太大了。我见过一个生产系统,单次请求要匹配 8000 多条规则,响应时间直接飙到 3 秒多。后来我们把规则集拆成 12 个小包,响应时间降到 200 毫秒以内。说白了,规则引擎的性能瓶颈,很多时候不是引擎本身不行,而是你喂给它的规则集太「胖」了。

这一章,我们就聊聊怎么把大规则集拆小、怎么分组分层、怎么设计优先级。这些都是我这些年踩坑踩出来的经验。

为什么大规则集必须拆?

先看一个简单的数学问题。假设你有 N 条规则,引擎每来一个事实,都要遍历这些规则做条件匹配。如果规则之间没有索引优化,复杂度就是 O(N)。N 从 100 涨到 1000,匹配时间涨 10 倍。这还不算规则之间的冲突检测、优先级排序、结果合并这些开销。

我在项目中遇到过最极端的情况:一个保险核保系统,单条规则集里塞了 15000 多条规则。每次规则加载就要 40 秒,匹配一次要 6 秒。用户点一下「核保」,等半天没反应。后来我们拆成 6 个子规则集,加载时间降到 2 秒,匹配时间降到 300 毫秒。

核心原则: 单个规则集的大小建议控制在 500 条以内。超过 1000 条,你就该考虑拆分了。

拆分策略一:按业务维度拆

这是最直观的拆分方式。把规则按照业务领域、产品线、场景等维度切分。每个子规则集只负责一个明确的业务范围。

举个例子,一个电商平台的促销规则引擎,可以这样拆:

子规则集名称 业务范围 规则数量 触发条件
满减规则 订单满额减价 120 条 订单金额 > 0
折扣规则 商品折扣、品类折扣 200 条 商品 ID 不为空
优惠券规则 优惠券发放与核销 80 条 用户有优惠券
赠品规则 满额赠品、买赠 50 条 订单金额 > 阈值
会员专享规则 会员等级专属优惠 60 条 用户等级 > 0

你看,每个子规则集都有明确的触发条件。引擎先判断触发条件,只加载匹配的子规则集。这样就不用每次都加载全部 500 多条规则了。

拆分策略二:按执行阶段拆

有些规则集虽然业务上是一体的,但执行逻辑有先后依赖关系。这时候可以按阶段拆。

我记得有个风控系统,规则执行分三个阶段:

  1. 预筛选阶段: 快速过滤掉明显不合规的请求。比如黑名单检查、IP 地域检查。这些规则执行快,命中率高。
  2. 详细评估阶段: 对通过预筛选的请求做深度分析。比如用户行为评分、交易模式匹配。这些规则计算量大。
  3. 决策输出阶段: 根据前两个阶段的结果,生成最终决策。比如「通过」「人工审核」「拒绝」。

这样拆的好处是:大部分请求在预筛选阶段就被拦截了,根本不需要进入详细评估。系统压力大大降低。

我的建议: 按阶段拆分时,把「快失败」的规则放在最前面。这样能尽早过滤掉无效请求,减少后续计算量。

规则分组与分层

拆完规则集之后,还要考虑怎么组织这些子规则集。我习惯用「分组 + 分层」的方式。

分组是把同一业务维度的规则放在一个组里。组内规则可以共享一些上下文数据,比如同一个事实对象、同一个结果集。

分层是给规则组定义执行顺序。比如第一层是全局规则,第二层是业务规则,第三层是特殊规则。每一层执行完后,结果可以传递给下一层。

下面这张图展示了分组与分层的结构:

规则分组与分层结构图 第一层:全局规则(预筛选) 黑名单检查组 | IP 地域组 | 设备指纹组 执行策略:并行执行,任一命中即拦截 第二层:业务规则(详细评估) 用户行为组 | 交易模式组 | 社交关系组 执行策略:顺序执行,累计评分 第三层:决策规则(结果输出) 通过组 | 人工审核组 | 拒绝组 执行策略:根据评分阈值选择决策路径 说明:每一层执行完后,结果会传递给下一层。上层拦截的请求不再进入下层。

规则优先级设计

优先级设计是规则引擎调优中最容易被忽视的一环。很多人觉得优先级就是给规则排个序,其实没那么简单。

优先级设计要解决三个问题:

  • 冲突解决: 多条规则同时匹配时,谁说了算?
  • 执行顺序: 规则之间的依赖关系怎么保证?
  • 性能优化: 高优先级规则能不能提前拦截,避免低优先级规则执行?

我常用的优先级设计模式有三种:

模式 适用场景 实现方式 注意事项
数值优先级 规则之间无依赖,只需决定谁先执行 给每条规则分配一个整数优先级(1-1000) 避免优先级相同,否则结果不确定
层级优先级 规则有明确的执行阶段 按层级分组,层级内再设数值优先级 层级间不要有循环依赖
条件优先级 优先级取决于运行时上下文 在规则条件中动态计算优先级 性能开销较大,需要缓存
我曾经踩过的坑: 有一次我把数值优先级设成了 1-100,结果业务方新增规则时,发现优先级不够用了。后来我改成 1-10000,留足扩展空间。建议优先级范围至少留 10 倍余量。

实战:一个完整的拆分案例

最后,我分享一个真实的拆分案例。这是一个金融产品的规则引擎,原始规则集有 3000 多条,我们把它拆成了这样:

规则集结构(拆分后):
├── 第一层:准入规则(500 条)
│   ├── 年龄检查组(100 条)
│   ├── 身份验证组(200 条)
│   └── 信用检查组(200 条)
├── 第二层:产品匹配规则(800 条)
│   ├── 存款产品组(300 条)
│   ├── 贷款产品组(300 条)
│   └── 理财产品组(200 条)
├── 第三层:定价规则(1200 条)
│   ├── 利率计算组(500 条)
│   ├── 费用计算组(400 条)
│   └── 优惠策略组(300 条)
└── 第四层:决策输出规则(500 条)
    ├── 通过组(200 条)
    ├── 拒绝组(200 条)
    └── 人工审核组(100 条)

每个子规则集都控制在 500 条以内。引擎根据用户请求的类型,只加载对应的子规则集。比如用户申请存款产品,就只加载「存款产品组」和「利率计算组」,其他组根本不加载。

结果怎么样?规则加载时间从 12 秒降到 1.5 秒,单次匹配时间从 2 秒降到 150 毫秒。系统吞吐量提升了 8 倍。

一个小技巧: 拆分规则集时,可以给每个子规则集加一个「版本号」。这样更新规则时,可以只重新加载变更的子规则集,不用全量加载。这在生产环境中非常实用。

嗯,规则集拆分这件事,说白了就是「分而治之」。把大问题拆成小问题,每个小问题单独优化。你想想看,一个 10000 条规则的集合,再怎么优化也跑不过 10 个 1000 条规则的集合。这就是拆分的核心价值。