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 组合中,把最可能命中的条件放前面。这不仅是性能问题,也影响日志可读性——短路后未执行的条件不会出现在命中详情里,排查时容易误判。

建议在规则引擎中为每个条件节点生成唯一路径标识(如 cond.0.and.2),命中日志中记录“哪些条件为真、哪些为假、哪些未执行”,这是后续规则调优最重要的数据来源。

三、优先级与命中策略:谁先说话,谁说了算

当一笔交易同时命中多条规则时,系统必须有明确的裁决策略。常见策略有四类:

策略行为适用场景风险
First-Match按优先级取第一条命中规则强规则、黑名单、硬拒绝低优先级规则永远不生效
All-Match + 聚合执行所有命中规则,按动作聚合评分卡、多信号融合性能开销大,需控制规则数
Score-Sum累加各规则分值,阈值决策风险评分、灰度拦截分值权重需持续校准
Override高优先级规则可覆盖低优先级结论白名单、VIP 豁免覆盖链路过长难排查

实战中推荐分层混合策略:第一层用 First-Match 处理硬拒绝/硬放行;第二层用 All-Match + Score-Sum 处理风险评分;第三层用 Override 处理白名单和人工干预。这样既保证强规则即时生效,又保留评分模型的弹性。

优先级数值不要用 1、2、3 这种连续整数。建议以 10 为步长(10、20、30…),预留插入空间。否则每次新增规则都要重排所有优先级,极易引发线上事故。

四、规则生命周期管理:从草稿到退役的闭环

规则不是写完就结束,它有自己的生命周期。一个成熟的规则平台应支持以下阶段:

  1. 草稿(DRAFT):业务方在沙箱中编写,不参与线上决策。
  2. 测试(TESTING):用历史流量回放,输出命中率、误杀率、覆盖率。
  3. 灰度(GRAY):按流量比例或用户分桶生效,观察真实指标。
  4. 全量(ACTIVE):正式参与决策,纳入监控告警。
  5. 观察(OBSERVE):只记录命中日志,不执行动作,用于评估下线影响。
  6. 退役(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%。

规则系统的核心不是“能写多少规则”,而是“能管好多少规则”。表达、组合、优先级、命中策略、生命周期,这五件事共同决定了规则系统是资产还是负债。