一、为什么要把风控拆成三个服务?

先说说背景。我最早做风控系统时,所有逻辑都塞在一个单体应用里。

决策引擎、名单管理、额度计算,全搅在一起。刚开始还好,业务量一上来,问题就暴露了。

举个例子:名单服务需要高频更新黑名单,每次更新都要重启整个应用。决策服务那边还在跑实时交易,一重启就丢请求。你说尴不尴尬?

所以,拆分是必然的。但怎么拆?我踩过不少坑。

二、拆成三个服务:决策、名单、额度

2.1 决策服务

这是核心。它负责接收请求、编排规则、输出结果。说白了,就是「大脑」。

我习惯把决策服务做得尽量「无状态」。所有规则配置、模型参数都从外部加载。这样扩缩容特别方便。

关键设计原则:

  • 决策服务不直接操作数据库
  • 所有数据通过 RPC 或消息队列获取
  • 规则引擎独立部署,支持热更新

2.2 名单服务

名单服务负责管理黑名单、白名单、灰名单。听起来简单,但坑不少。

我记得有一次,名单服务的内存缓存和数据库不一致,导致一个已经被拉黑的用户成功通过了审核。嗯,这事后来成了复盘的重点。

避坑指南:

我曾经在名单服务里直接用了 Redis 的过期策略,结果大量 key 同时过期,缓存雪崩。后来改成「主动过期 + 惰性删除」结合,才稳住。

2.3 额度服务

额度服务负责计算用户的可贷额度、可用额度。这个服务对一致性要求极高。

你想想看,如果两个请求同时修改额度,数据不一致了怎么办?

我建议用乐观锁 + 版本号机制。代码示例如下:

// 额度更新示例
public boolean updateQuota(String userId, double amount, long version) {
    String sql = "UPDATE user_quota SET amount = amount - ?, version = version + 1 " +
                 "WHERE user_id = ? AND version = ?";
    int rows = jdbcTemplate.update(sql, amount, userId, version);
    return rows > 0;
}

三、拆分后我踩过的坑

3.1 坑一:服务间调用超时

拆分后,决策服务要调名单服务,名单服务要调额度服务。调用链一长,超时问题就来了。

我记得有一次,名单服务因为 GC 停顿了 2 秒,导致决策服务大量超时。整个风控链路都堵住了。

我的解决方案:

  • 所有 RPC 调用设置合理的超时时间(建议 500ms 以内)
  • 引入熔断机制,防止雪崩
  • 关键路径上做本地缓存兜底

3.2 坑二:数据一致性

三个服务各自维护自己的数据库。但有些数据是共享的,比如用户的黑名单状态。

我曾经遇到过:名单服务更新了黑名单,但决策服务读到的还是旧数据。结果放行了一个高风险用户。

怎么解决?我建议用「最终一致性」方案:

  • 使用消息队列同步变更事件
  • 决策服务定期刷新本地缓存
  • 关键场景下强制走主库查询

3.3 坑三:部署和运维复杂度

一个服务变成三个服务,部署、监控、日志收集的复杂度翻倍。

我刚开始没注意,结果每次上线都要手动改配置,累得够呛。

建议:

  • 统一配置中心(如 Apollo、Nacos)
  • 标准化日志格式,方便链路追踪
  • 每个服务独立部署,互不影响

四、架构图:三个服务的协作关系

下面这张图展示了决策、名单、额度三个服务的核心协作流程:

决策服务 规则引擎 / 模型 名单服务 黑/白/灰名单 额度服务 额度计算 / 冻结 查询名单 查询/扣减额度 异步通知 异步通知 图例 同步调用 异步通知 决策服务 名单服务 额度服务

五、总结一下

微服务拆分不是银弹。拆得好,系统灵活、可扩展。拆不好,复杂度翻倍、问题频出。

我个人觉得,最关键的是想清楚「边界」。每个服务该做什么、不该做什么,一开始就要定好。

另外,别追求完美。先拆出最核心的决策服务,名单和额度可以慢慢剥离。迭代着来,比一步到位稳妥得多。

最后送大家一句话:

拆分是为了解决问题,不是为了炫技。如果单体应用够用,就别瞎折腾。


公众号:蓝海资料掘金营,微信deep3321