4、规则定义语言:规则语法设计、条件表达式、动作定义、规则优先级与冲突解决

各位好,我是老张。今天咱们聊聊规则定义语言——说白了,就是怎么让机器听懂你的风控逻辑。

我见过不少团队,规则引擎搞得很复杂,但最后发现是规则定义本身出了问题。语法设计得不好,业务人员看不懂,开发人员改不动,运维人员查不了。嗯,这其实是个沟通问题。

4.1 规则语法设计:让业务人员也能读懂

我个人习惯,规则语法一定要贴近自然语言。你想想看,风控规则是谁在维护?大部分时候是业务专家,不是程序员。所以语法设计的第一原则是:可读性优先

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

RULE "温度过高保护"
WHEN
    sensor.temperature > 85.0
    AND duration > 5
THEN
    ACTION: shutdown(priority=HIGH)
    ACTION: log("温度异常", level=WARN)
    ACTION: alert(recipient="ops_team")
END

你看,WHEN后面是条件,THEN后面是动作。业务人员一看就懂:温度超过85度持续5秒以上,就关机、记录、报警。

我在项目中遇到过,有些团队喜欢用JSON或XML来定义规则。说实话,机器解析是方便了,但人看着真头疼。比如:

{
  "rule": "temp_protect",
  "condition": {
    "operator": "AND",
    "operands": [
      {"field": "sensor.temperature", "op": ">", "value": 85.0},
      {"field": "duration", "op": ">", "value": 5}
    ]
  },
  "actions": [
    {"type": "shutdown", "params": {"priority": "HIGH"}},
    {"type": "log", "params": {"message": "温度异常", "level": "WARN"}}
  ]
}

这玩意儿,业务人员看了直接摇头。所以我的建议是:内部存储用结构化数据,对外展示用DSL。两者之间做个双向转换,既方便机器处理,也方便人阅读。

核心设计原则:
  • 关键字大写(RULE, WHEN, THEN, AND, OR, NOT)
  • 字段路径用点号分隔(sensor.temperature)
  • 比较运算符直观(>, <, ==, !=, >=, <=)
  • 动作列表用缩进或序号区分
  • 支持注释(# 或 //)

4.2 条件表达式:从简单比较到复杂逻辑

条件表达式是规则的核心。说白了,就是判断「什么时候该触发」。我把它分成三个层次:

4.2.1 基础比较表达式

最简单的形式:字段 + 运算符 + 值。比如:

sensor.voltage < 3.3
device.temperature >= 85.0
status.code == 0xE001

这里要注意数据类型。我曾经踩过一个坑:温度值是浮点数,但规则里写成了整数比较。结果85.0和85在浮点运算里不完全等价,导致规则没触发。后来我强制要求:所有数值比较必须显式声明类型

4.2.2 复合条件表达式

用AND、OR、NOT组合多个条件。我习惯用括号明确优先级,避免歧义:

WHEN
    (sensor.temperature > 85.0 OR sensor.current > 2.0)
    AND NOT device.status == "maintenance"
THEN
    ...

这里有个小技巧:AND的优先级高于OR,跟大多数编程语言一致。但为了保险,我建议业务人员写规则时,复杂条件一律加括号。

4.2.3 时间窗口与聚合表达式

硬件风控里,很多规则跟时间有关。比如「1分钟内连续3次过流」。这种就需要时间窗口支持:

WHEN
    count(sensor.overcurrent, window=60s) >= 3
THEN
    ...

还有聚合函数:avg、max、min、sum。比如:

WHEN
    avg(sensor.temperature, window=300s) > 80.0
THEN
    ...
我的经验:时间窗口的粒度不要太小。我曾经设过1秒窗口,结果高频采样导致规则引擎CPU跑满。后来改成5秒窗口,效果好了很多。

4.3 动作定义:规则触发后做什么

动作就是规则触发后的执行逻辑。我把它分成三类:

动作类型 说明 示例
控制动作 直接操作硬件 shutdown, reset, throttle
通知动作 发送告警或日志 alert, log, email
数据动作 记录或更新状态 set_flag, increment_counter

动作定义时,我建议加上参数校验。比如shutdown动作必须带priority参数,取值范围是LOW/MEDIUM/HIGH/CRITICAL。如果漏了参数,规则引擎应该直接报错,而不是默默执行。

ACTION: shutdown(priority=HIGH, reason="温度超限")
ACTION: log(message="设备异常", level=ERROR, source="sensor_01")
ACTION: set_flag(name="overheat", value=true, ttl=3600)

这里有个细节:动作的执行顺序。我一般按优先级排序:先执行控制动作(比如关机),再执行通知动作(比如发告警)。如果反过来,告警发出去了但关机失败了,那就尴尬了。

注意:动作定义里不要写复杂逻辑。规则引擎只负责「触发什么动作」,不负责「动作怎么实现」。具体实现交给后端服务。否则规则引擎会变得臃肿难维护。

4.4 规则优先级与冲突解决

当多条规则同时满足条件时,谁先执行?这就是优先级问题。我见过最惨的一次:两条规则同时触发,一条要关机,一条要降频,结果设备来回切换,直接挂了。

4.4.1 优先级设计

我习惯用数字表示优先级,数字越小优先级越高:

RULE "紧急关机" PRIORITY 1
WHEN sensor.temperature > 100.0
THEN ACTION: shutdown()

RULE "降频保护" PRIORITY 10
WHEN sensor.temperature > 85.0
THEN ACTION: throttle(50%)

当温度超过100度时,两条规则都满足。但优先级1的「紧急关机」先执行,设备关机后,「降频保护」虽然也满足条件,但设备已经关了,动作自然无效。

4.4.2 冲突解决策略

冲突分两种:互斥冲突重叠冲突

  • 互斥冲突:两条规则的动作完全相反。比如一条要开机,一条要关机。这种必须通过优先级解决,或者直接报错让用户修改。
  • 重叠冲突:两条规则的动作有部分重叠。比如一条要降频50%,另一条要降频30%。我建议取最严格的动作(即降频50%),因为硬件风控以安全为第一。

我曾经在项目中实现过一个冲突检测器:规则部署时自动扫描,发现互斥冲突直接拒绝部署,并给出冲突规则ID。这招很管用,上线后冲突问题减少了80%。

4.4.3 规则分组与覆盖

当规则数量超过100条时,全局优先级就不够用了。我建议引入规则组

GROUP "温度保护" PRIORITY 10
    RULE "高温关机" PRIORITY 1
    RULE "中温降频" PRIORITY 2
END

GROUP "电流保护" PRIORITY 20
    RULE "过流关机" PRIORITY 1
    RULE "过流降频" PRIORITY 2
END

这里,组优先级决定哪个组先执行,组内规则优先级决定组内执行顺序。这样管理起来清晰多了。

冲突解决最佳实践:
  1. 规则部署时做静态冲突检测
  2. 运行时按优先级排序执行
  3. 高优先级规则执行后,低优先级规则自动跳过(如果动作互斥)
  4. 记录所有触发记录,方便事后审计

4.5 知识体系总览

下面这张图是我自己画的,把规则定义语言的核心要素串起来了:

规则定义语言核心架构 规则语法设计 DSL语法 · 可读性 · 结构化 条件表达式 比较 · 复合 · 时间窗口 动作定义 控制 · 通知 · 数据 规则优先级与冲突解决 优先级数字 · 互斥检测 · 规则分组 规则引擎运行时 规则执行结果:动作序列 + 审计日志 规则定义 → 条件匹配 → 优先级排序 → 冲突解决 → 动作执行

这张图把规则定义语言的核心流程串起来了。从语法设计开始,到条件表达式、动作定义,再到优先级和冲突解决,最后交给规则引擎运行时去执行。每一步都环环相扣。

好了,关于规则定义语言,我就讲这么多。记住一句话:好的规则定义语言,应该是业务人员能写、开发人员能改、机器能高效执行。做到这三点,你的规则引擎就成功了一半。


专注资料整理