4. 规则系统入门
在实时反欺诈决策链路中,规则系统是最早落地、最容易被业务方理解、也最容易被低估的一层。很多团队以为规则就是“写几个 if-else”,结果上线三个月后规则数量膨胀到几千条,互相冲突、无法解释、没人敢删。要避免这种局面,必须从第一天就把规则当作一种可管理的资产来设计:它有表达语法、有组合逻辑、有优先级、有命中策略,也有完整的生命周期。
一、核心原理:规则的本质是“条件 + 动作 + 元数据”
一条生产级规则,绝不只是条件判断。它至少包含四部分:条件表达式(什么情况下触发)、动作(触发后做什么,如拒绝、挑战、标记、打分)、优先级(多条规则同时命中时谁先说话)、元数据(规则ID、版本、负责人、生效时间、灰度比例、命中统计)。缺少元数据的规则,在出问题时无法追溯,在优化时无法评估。
规则表达通常有两种风格:声明式表达式(如 JSON、DSL、SQL-like)和脚本式(如 Groovy、Lua、Aviator)。声明式更安全、易审计、易被业务配置;脚本式更灵活,但容易写出性能黑洞和不可控逻辑。实战中推荐“声明式为主、脚本式为辅”,把 90% 的规则限制在可枚举的算子内。
{
"ruleId": "R-10023",
"name": "夜间大额异地登录转账",
"priority": 80,
"condition": {
"and": [
{ "field": "login.hour", "op": "between", "value": [0, 6] },
{ "field": "login.city", "op": "!=", "value": "user.homeCity" },
{ "field": "transfer.amount", "op": ">", "value": 5000 },
{ "field": "device.isNew", "op": "==", "value": true }
]
},
"action": { "decision": "REJECT", "score": 90, "reason": "NIGHT_REMOTE_NEW_DEVICE" },
"status": "ACTIVE",
"version": 3,
"owner": "risk-team-a",
"effectiveFrom": "2025-01-10T00:00:00Z"
}
二、条件组合:AND / OR / NOT 与嵌套的工程约束
条件组合看似简单,但工程上必须设定边界,否则规则会变成不可维护的“逻辑迷宫”。建议遵循三条约束:
- 嵌套深度不超过 3 层:超过 3 层的条件几乎无法被业务方复核,也容易在序列化/反序列化时出错。
- 单条规则条件数不超过 20 个:过多条件意味着你在用一条规则覆盖多个场景,应拆分为多条规则。
- 禁止在条件中做跨事件聚合:如“过去 1 小时同设备登录次数 > 5”,这类逻辑应交给特征平台预计算,规则只消费结果字段。
条件组合的另一个关键是短路求值。AND 组合中,把最便宜、过滤率最高的条件放前面;OR 组合中,把最可能命中的条件放前面。这不仅是性能问题,也影响日志可读性——短路后未执行的条件不会出现在命中详情里,排查时容易误判。
三、优先级与命中策略:谁先说话,谁说了算
当一笔交易同时命中多条规则时,系统必须有明确的裁决策略。常见策略有四类:
| 策略 | 行为 | 适用场景 | 风险 |
|---|---|---|---|
| First-Match | 按优先级取第一条命中规则 | 强规则、黑名单、硬拒绝 | 低优先级规则永远不生效 |
| All-Match + 聚合 | 执行所有命中规则,按动作聚合 | 评分卡、多信号融合 | 性能开销大,需控制规则数 |
| Score-Sum | 累加各规则分值,阈值决策 | 风险评分、灰度拦截 | 分值权重需持续校准 |
| Override | 高优先级规则可覆盖低优先级结论 | 白名单、VIP 豁免 | 覆盖链路过长难排查 |
实战中推荐分层混合策略:第一层用 First-Match 处理硬拒绝/硬放行;第二层用 All-Match + Score-Sum 处理风险评分;第三层用 Override 处理白名单和人工干预。这样既保证强规则即时生效,又保留评分模型的弹性。
四、规则生命周期管理:从草稿到退役的闭环
规则不是写完就结束,它有自己的生命周期。一个成熟的规则平台应支持以下阶段:
- 草稿(DRAFT):业务方在沙箱中编写,不参与线上决策。
- 测试(TESTING):用历史流量回放,输出命中率、误杀率、覆盖率。
- 灰度(GRAY):按流量比例或用户分桶生效,观察真实指标。
- 全量(ACTIVE):正式参与决策,纳入监控告警。
- 观察(OBSERVE):只记录命中日志,不执行动作,用于评估下线影响。
- 退役(RETIRED):停止生效,保留元数据和历史命中记录。
每个阶段都应有明确的准入准出条件。例如从灰度到全量,要求“误杀率 < 0.1% 且命中量 > 1000 笔”;从 ACTIVE 到 OBSERVE,要求“连续 30 天命中量 < 10 笔”。没有量化门槛,规则就会只增不减。
# 规则状态流转示例(伪配置)
rule R-10023:
status: GRAY
gray:
ratio: 0.05
bucketBy: userId
metrics:
hitCount: 1280
falsePositiveRate: 0.0008
coverage: 0.012
promoteTo: ACTIVE
promoteCondition: "falsePositiveRate < 0.001 AND hitCount > 1000"
五、案例与复盘:一次“规则雪崩”的教训
某支付团队在大促前一周新增了 47 条规则,全部设为优先级 50,采用 All-Match 策略。大促当天,一笔正常交易同时命中 12 条规则,评分累加后触发拒绝,导致大量误杀。复盘发现三个问题:一是优先级未分层,所有规则平级;二是缺少 Score-Sum 的封顶机制;三是灰度阶段只看了命中率,没看误杀率。
修复方案:引入优先级分层(硬规则 10、评分规则 50、观察规则 90);评分累加设置上限 100;灰度阶段强制要求误杀率指标;新增规则必须经过历史流量回放。上线后误杀率从 1.2% 降到 0.05%。