4. 规则语言与语法:DRL 基础
Drools Rule Language(DRL)是风控规则引擎的核心表达语言。在联合推理场景中,DRL 负责将风控策略、模型阈值、名单匹配等逻辑编译为可执行的规则网络。掌握 DRL 的基础语法,是构建高效、可维护规则集的前提。
4.1 DRL 文件基本结构
一个标准的 DRL 文件由 package、import、global 和若干 rule 块组成。以下是一个风控场景的完整示例:
package com.riskengine.rules
import com.riskengine.model.TransactionEvent
import com.riskengine.model.RiskResult
import global com.riskengine.service.RiskService riskService
rule "高频交易拦截"
salience 100
no-loop true
when
$tx: TransactionEvent(amount > 10000, userId != null)
$count: Long(this >= 5) from accumulate(
TransactionEvent(userId == $tx.userId, timestamp after $tx.timestamp - 1h),
count(1)
)
then
RiskResult result = new RiskResult();
result.setRuleName("高频交易拦截");
result.setDecision("REJECT");
result.setReason("1小时内交易次数超过5次,金额大于10000");
riskService.saveResult(result);
retract($tx);
end
| 关键字 | 作用 | 风控场景示例 |
|---|---|---|
| package | 定义规则所属命名空间 | com.riskengine.rules.online |
| import | 引入 Java 类或模型 | import com.riskengine.model.TransactionEvent |
| global | 声明全局服务或变量 | global RiskService riskService |
| rule … end | 规则体边界 | rule "黑名单匹配" … end |
| when | 条件部分(LHS) | when $tx: TransactionEvent(amount > 5000) |
| then | 动作部分(RHS) | then riskService.block($tx); |
4.2 条件(when)——模式匹配与约束
when 部分是规则引擎的“眼睛”。它通过模式匹配在 Working Memory 中查找满足条件的 Fact 对象。风控场景中,Fact 通常是交易事件、用户画像、设备指纹等。
4.2.1 基本模式与字段约束
// 单模式:匹配金额大于5000的交易
$tx: TransactionEvent(amount > 5000)
// 多字段复合约束
$tx: TransactionEvent(amount > 5000, currency == "CNY", channel != "OFFLINE")
// 嵌套属性访问
$user: UserProfile( address.city == "北京" )
// 使用 || 和 && 组合条件
$tx: TransactionEvent( (amount > 10000 && amount < 50000) || riskLevel == "HIGH" )
4.2.2 跨模式关联与 accumulate
风控规则经常需要跨多个 Fact 做聚合判断,例如“同一设备在 5 分钟内登录超过 3 个不同账号”。DRL 通过 accumulate 实现:
rule "设备关联账号过多"
when
$login: LoginEvent(deviceId != null)
$count: Long(this > 3) from accumulate(
LoginEvent(deviceId == $login.deviceId,
timestamp after $login.timestamp - 5m,
accountId != $login.accountId),
count(distinct(accountId))
)
then
riskService.flagDevice($login.getDeviceId(), "MULTI_ACCOUNT");
end
after 操作符使用时间戳偏移量,引擎内部通过时间窗口索引优化性能。在风控高并发场景下,优先使用 after/before 而非手动比较时间戳。
4.3 动作(then)——推理结果输出
then 部分是规则的“手”。当条件满足时,引擎执行 then 块中的 Java 代码。在风控联合推理中,then 通常完成以下三类动作:
- 决策输出:构造 RiskResult 对象,设置决策码(ACCEPT/REVIEW/REJECT)和原因码。
- 事实操作:使用
insert()插入新 Fact(如风险标签),update()修改已有 Fact,retract()删除 Fact。 - 服务调用:通过 global 变量调用外部服务(如写入风控事件表、触发告警)。
then
// 1. 构造决策结果
RiskResult result = new RiskResult();
result.setTransactionId($tx.getTransactionId());
result.setDecision("REVIEW");
result.setRiskScore(85);
// 2. 插入新 Fact,触发下游规则
insert(result);
// 3. 调用外部服务
riskService.sendAlert($tx.getUserId(), "高风险交易");
// 4. 删除当前 Fact,避免重复匹配
retract($tx);
end
4.4 规则属性:salience 与 no-loop
规则属性控制引擎的推理行为。在风控规则链中,salience 和 no-loop 是最常用的两个属性。
4.4.1 salience——规则优先级
salience 定义规则的执行优先级,值越大优先级越高。默认值为 0。在风控场景中,通常将拦截类规则设为高优先级,打标类规则设为中优先级,统计类规则设为低优先级。
| 优先级层级 | salience 值 | 典型规则 | 执行顺序 |
|---|---|---|---|
| 最高 | 1000 | 黑名单命中、金额超限 | 最先执行 |
| 中 | 500 | 设备指纹异常、IP 风险 | 中间执行 |
| 低 | 100 | 行为统计、模型分聚合 | 最后执行 |
| 默认 | 0 | 未显式设置 | 按自然顺序 |
rule "黑名单直接拒绝"
salience 1000
when
$tx: TransactionEvent(userId in ("U001", "U002", "U003"))
then
riskService.reject($tx, "BLACKLIST");
end
rule "模型分辅助判断"
salience 100
when
$tx: TransactionEvent(amount > 5000)
$score: Integer(this > 80) from riskService.getModelScore($tx)
then
riskService.review($tx, $score);
end
4.4.2 no-loop——防止无限递归
当 then 块中执行 update() 或 insert() 时,引擎会重新激活所有匹配的规则。如果规则 A 更新了 Fact,而规则 A 本身仍然满足条件,就会形成无限循环。no-loop=true 告诉引擎:当前规则被激活后,即使它修改了 Fact,也不要再次激活它自己。
activation-group 或 lock-on-active 属性。
rule "风险等级升级"
salience 200
no-loop true
when
$tx: TransactionEvent(riskLevel == "MEDIUM", amount > 20000)
then
$tx.setRiskLevel("HIGH");
update($tx); // 更新后不会重新触发本规则
riskService.notifyRiskUpgrade($tx);
end
4.5 DRL 规则执行流程(SVG)
下图展示了 DRL 规则从加载到触发的完整生命周期,以及 salience 和 no-loop 在其中的作用点:
4.6 属性组合实战:风控规则链设计
在真实风控项目中,规则不是孤立执行的,而是形成一条决策链。以下是一个典型的联合推理规则链设计:
// 规则1:黑名单拦截(最高优先级,不循环)
rule "黑名单拦截"
salience 1000
no-loop true
when
$tx: TransactionEvent(blacklisted == true)
then
riskService.reject($tx, "BLACKLIST");
retract($tx); // 移除事实,后续规则不再匹配
end
// 规则2:金额阈值+模型分(中优先级)
rule "金额与模型联合判断"
salience 500
no-loop true
when
$tx: TransactionEvent(amount > 10000, blacklisted == false)
$score: Integer(this > 70) from riskService.getModelScore($tx)
then
$tx.setRiskLevel("HIGH");
update($tx); // 触发规则3
end
// 规则3:高风险打标(低优先级,仅执行一次)
rule "高风险打标"
salience 100
no-loop true
activation-group "tagging"
when
$tx: TransactionEvent(riskLevel == "HIGH")
then
riskService.tagTransaction($tx, "HIGH_RISK_AMOUNT");
end
retract() 在拦截规则中移除 Fact,可以阻断后续低优先级规则的匹配,提升引擎性能。使用 activation-group 确保同一组内只有一个规则被触发,避免重复打标。
4.7 DRL 语法速查表
| 语法元素 | 示例 | 说明 |
|---|---|---|
| 字段约束 | amount > 1000 |
支持 >, <, >=, <=, ==, != |
| 字符串匹配 | userId in ("U01","U02") |
in 操作符匹配集合 |
| 正则匹配 | email matches "[a-z]+@.*" |
matches 使用 Java 正则 |
| 时间操作符 | timestamp after $tx.timestamp - 1h |
after/before 支持时间偏移 |
| 类型转换 | this instanceof RiskEvent |
instanceof 做类型判断 |
| not 存在 | not (TransactionEvent(amount > 50000)) |
检查不存在匹配的事实 |
| exists 存在 | exists (TransactionEvent(amount > 50000)) |
检查至少存在一个匹配 |
| accumulate | accumulate(…, count(1)) |
聚合函数:count/sum/avg/min/max |
| from | $score: Integer() from riskService.getScore() |
从外部方法获取数据 |