4. 规则语言与语法:DRL 基础

Drools Rule Language(DRL)是风控规则引擎的核心表达语言。在联合推理场景中,DRL 负责将风控策略、模型阈值、名单匹配等逻辑编译为可执行的规则网络。掌握 DRL 的基础语法,是构建高效、可维护规则集的前提。

核心认知:DRL 是一种声明式语言——你只需描述“当什么条件发生时,执行什么动作”,引擎自动决定何时以及如何触发规则。这与传统 if-else 命令式编程有本质区别。

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
实战建议:accumulate 中的 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,也不要再次激活它自己。

常见陷阱:no-loop 只阻止规则自身的重新激活,不阻止其他规则的激活。如果规则 A 和规则 B 互相 update 对方依赖的 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 在其中的作用点:

DRL 规则执行流程与属性作用点 1. 规则编译 2. Working Memory 3. 模式匹配 (when) 4. 冲突解决 (salience) 5. 动作执行 (then) 6. 循环检测 (no-loop) update/insert 触发重新匹配 salience 在冲突解决阶段决定规则执行顺序;no-loop 在动作执行后阻止当前规则被自身 update 再次激活

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() 从外部方法获取数据