规则存储方案:数据库、文件与配置中心

规则写好了,加载器也搭起来了。接下来有个很现实的问题——规则放哪儿?

我见过不少团队,一开始图省事,把规则硬编码在代码里。结果呢?改一条规则就要重新发版,运维同学半夜被叫起来上线,苦不堪言。说白了,规则存储选型,直接决定了你的风控系统是「活」的还是「死」的。

今天咱们就聊聊三种主流方案:数据库存储、文件存储、配置中心。每种方案我都踩过坑,咱们一个一个说。

一、数据库存储(MySQL / PostgreSQL)

数据库存储是最常见的方案。我个人习惯用 PostgreSQL,但 MySQL 也完全够用。核心思路就是把规则当成数据,存在表里。

表结构设计

先看一个简单的规则表设计:

CREATE TABLE risk_rule (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    rule_code VARCHAR(64) NOT NULL UNIQUE COMMENT '规则编码',
    rule_name VARCHAR(128) NOT NULL COMMENT '规则名称',
    rule_content TEXT NOT NULL COMMENT '规则内容(JSON格式)',
    rule_version INT DEFAULT 1 COMMENT '版本号',
    status TINYINT DEFAULT 1 COMMENT '状态:1启用 0禁用',
    effective_time DATETIME COMMENT '生效时间',
    expire_time DATETIME COMMENT '失效时间',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

嗯,这里要注意:rule_content 字段我建议存 JSON 格式。为什么?因为规则结构经常变,用 JSON 可以灵活扩展字段,不用频繁改表结构。

我的经验: 曾经有个项目,规则字段从 5 个涨到 20 个,如果用固定列,光改表就改了 3 次。后来全改成 JSON,世界清净了。

查询与缓存

数据库存储的好处是查询灵活。你可以按状态、按时间、按规则编码精确查找。但有个坑——高并发下,每次请求都查数据库,数据库扛不住。

我的做法是:

  • 规则加载到本地缓存(比如 Guava Cache 或 Caffeine)
  • 设置合理的过期时间(比如 5 分钟)
  • 通过数据库的 update_time 字段判断是否需要刷新
// 伪代码示例
public List<Rule> loadActiveRules() {
    String sql = "SELECT * FROM risk_rule WHERE status = 1 " +
                 "AND effective_time <= NOW() " +
                 "AND (expire_time IS NULL OR expire_time > NOW())";
    return jdbcTemplate.query(sql, ruleRowMapper);
}
避坑指南: 我曾经遇到过数据库时间与应用服务器时间不一致,导致规则提前生效或延迟失效。解决方案:统一使用数据库时间,或者用时间戳字符串。

二、文件存储(YAML / JSON)

文件存储适合规则数量不多、变更不频繁的场景。比如你只有几十条规则,用 YAML 或 JSON 文件管理,简单直接。

YAML 示例

rules:
  - code: "RULE_001"
    name: "登录频率限制"
    condition:
      field: "login_count"
      operator: "gt"
      value: 10
    action: "block"
    priority: 1

  - code: "RULE_002"
    name: "异地登录检测"
    condition:
      field: "ip_city"
      operator: "neq"
      value: "last_login_city"
    action: "verify"
    priority: 2

文件存储的好处是——你想想看,用 Git 管理规则文件,天然就有版本控制。谁改了哪条规则,什么时候改的,一目了然。

但缺点也很明显:

  • 分布式环境下,文件同步是个麻烦事
  • 规则多了以后,文件会变得很大,加载慢
  • 不支持动态修改——你得重启应用或者写个文件监听器
我的建议: 文件存储适合做「规则模板」或「初始规则集」。比如系统第一次启动时,从文件加载默认规则,后续变更走数据库或配置中心。

文件监听实现

// 使用 Commons-IO 的 FileAlterationMonitor
FileAlterationMonitor monitor = new FileAlterationMonitor(5000);
FileAlterationObserver observer = new FileAlterationObserver("rules/");
observer.addListener(new FileAlterationListenerAdaptor() {
    @Override
    public void onFileChange(File file) {
        // 重新加载规则
        reloadRules(file);
    }
});
monitor.addObserver(observer);
monitor.start();

三、配置中心(Apollo / Nacos)

到了微服务架构下,配置中心几乎是标配。我个人强烈推荐用配置中心来管理风控规则,尤其是规则变更频繁的场景。

为什么选配置中心?

特性 数据库 文件 配置中心
实时生效 需轮询或监听 需文件监听 推模式,秒级生效
版本管理 需自己实现 Git 天然支持 内置版本回滚
分布式支持 一般
操作门槛

Nacos 配置示例

以 Nacos 为例,规则可以存储在配置中心的一个 DataId 里:

// Nacos 配置监听
@NacosConfigListener(dataId = "risk-rules.yaml", type = ConfigType.YAML)
public void onRuleChange(String newConfig) {
    // 解析 YAML 为规则对象
    List<Rule> rules = yamlMapper.readValue(newConfig, 
        new TypeReference<List<Rule>>() {});
    // 更新本地规则缓存
    ruleCache.refresh(rules);
    log.info("规则已更新,当前规则数:{}", rules.size());
}
核心优势: 配置中心采用「推模式」。你在控制台改一条规则,所有服务节点几乎同时收到通知。不用轮询,不用重启,不用手动同步。

Apollo 的命名空间设计

Apollo 有个很实用的功能——命名空间。我习惯这样划分:

  • 公共命名空间: 存放所有服务共享的规则(比如全局黑名单)
  • 私有命名空间: 每个服务自己的规则(比如支付服务的风控规则)
  • 环境命名空间: 区分 dev、test、prod 的规则配置

这样做的好处是,不同团队可以独立管理自己的规则,互不干扰。

避坑指南: 我曾经在 Apollo 里存了 2000 多条规则,结果每次变更都推送全量数据,网络开销很大。后来改成增量更新——只推送变更的规则 ID,客户端自己拉取详情。性能提升明显。

三种方案如何选?

我画了一张图,帮你快速决策:

规则存储方案选型决策 规则数量 < 100? 是 → 文件存储 否 → 继续判断 变更频繁? 是 → 配置中心 否 → 数据库存储 注:实际选型还需考虑团队技术栈、运维能力等因素

说白了,没有银弹。我个人的经验是:

  • 初创项目、规则少: 先用文件存储,快速验证
  • 规则多了、需要查询: 迁移到数据库
  • 微服务架构、变更频繁: 直接上配置中心,一步到位

很多团队会混合使用——文件存模板,数据库存运行时规则,配置中心做热更新。嗯,这也是个不错的思路。


专注资料整理