规则存储方案:数据库、文件与配置中心
规则写好了,加载器也搭起来了。接下来有个很现实的问题——规则放哪儿?
我见过不少团队,一开始图省事,把规则硬编码在代码里。结果呢?改一条规则就要重新发版,运维同学半夜被叫起来上线,苦不堪言。说白了,规则存储选型,直接决定了你的风控系统是「活」的还是「死」的。
今天咱们就聊聊三种主流方案:数据库存储、文件存储、配置中心。每种方案我都踩过坑,咱们一个一个说。
一、数据库存储(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,客户端自己拉取详情。性能提升明显。
三种方案如何选?
我画了一张图,帮你快速决策:
说白了,没有银弹。我个人的经验是:
- 初创项目、规则少: 先用文件存储,快速验证
- 规则多了、需要查询: 迁移到数据库
- 微服务架构、变更频繁: 直接上配置中心,一步到位
很多团队会混合使用——文件存模板,数据库存运行时规则,配置中心做热更新。嗯,这也是个不错的思路。