规则引擎的"算法"其实不是一个单一算法,而是三个简单机制拼起来的:
一、执行模型:责任链 + 短路求值
规则不是把所有维度一次性打分再综合判断,而是按类型(领取/门槛/叠加/计价)分成一条条链,链上的规则按优先级排好顺序,从头开始逐条求值。只要有一条不通过,立刻停止,后面的规则根本不会被执行。这样做两个好处:一是省计算,大部分场景第一条不通过的规则往往排在前面就能拦住;二是"因为哪条规则被拒绝"天然有唯一确定的答案,不会出现好几条规则都不通过、不知道该给用户看哪个原因的情况。
二、表达模型:统一接口 + 两种求值方式
所有规则背后都实现同一个"输入上下文、输出通过与否"的接口,但内部求值方式不同。结构化规则(互斥组、数量上限)本质是直接在代码里写好的逻辑判断,输入什么类型就按什么逻辑走。表达式规则则是运行时的一个小型解释器:配置的字符串在编译阶段被解析成一棵语法树,求值时对着这棵树递归计算,碰到"订单金额""用户等级"这类字段引用,就去当前上下文里取对应的值代进去。因为两者对外都是同一个接口,链上可以随意混排,规则引擎本身不需要关心某条规则到底是硬编码的还是配置出来的。
三、一致性模型:整体编译 + 原子替换
规则变更不是"改一条生效一条",而是把这一批新规则整体重新编译成一份全新的、不可变的规则集合,编译全部成功才用一个原子指针把"当前生效的规则集合"整体切换过去。切换的一瞬间,正在处理中的老请求仍然用切换前的那份规则跑完,不会出现跑到一半规则变了的中间状态;读取规则的一方也完全不需要加锁,只是读一个指针。这本质上是一种"整体重建、原子替换"的无锁方案,好处是新规则里哪怕只有一条写错导致编译失败,也是整批回退,不会出现新旧规则混杂生效的情况。
四、冲突检测:集合求交
规则之间是否矛盾,判断方式很朴素——把每个券模板所在的互斥组做成一个"模板 → 它所属的组"的索引,两个模板是否互斥,就看它们各自所在的组集合有没有交集;而"必须同时使用"和"不能同时使用"这两组模板 ID,如果本身就有重叠,那就是配置自相矛盾,直接在提交时拦截,不需要等真正跑规则的时候才发现。