ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Spring Boot构建实时反欺诈平台:从规则引擎到Flowable审核流实践

基于Spring Boot构建实时反欺诈平台:从规则引擎到Flowable审核流实践 去年我们交易业务线的欺诈投诉开始成倍增长盗号、刷单、恶意退款各种手段轮着来风控部门天天找研发要数据。我被叫过去梳理了一周最后结论很明确不能再靠一堆散落的定时任务和数据库临时查询来对抗欺诈需要一个真正能实时决策的反欺诈平台。那段时间我基于Spring Boot从零搭了一套过程中踩了不少坑也沉淀了一些我认为值得分享的经验。这篇文章不是教科书式的系统设计文档就是一套已经跑在生产环境里的真实方案。我会从业务拆解讲起一直聊到架构选型、规则引擎、Flowable审核流、性能优化和具体的踩坑记录适合正在做风控系统、或者准备把反欺诈从“临时查库”升级成“平台化”的团队参考。1. 反欺诈平台要解决的核心问题拆解1.1 欺诈场景盘点哪些行为必须在毫秒级拦住反欺诈平台不是做一个“大而全”的系统而是在明确回答一个问题哪些请求、在什么时间点、需要做出什么决策。我当时的做法是先把欺诈场景全部罗列出来不急着写代码。常见的几类场景盗号登录攻击者通过撞库拿到账号密码模拟正常用户登录。这类行为必须在登录接口做实时检测慢一点都不行因为攻击者拿到账号后第一件事就是改密码、提现。批量注册注册环节被羊毛党用脚本刷量批量注册小号领取新人优惠。注册接口必须实时识别设备指纹、IP风险、注册频次。刷单和恶意退款电商场景的典型问题。下单时可能正常但收货后恶意退款。这类需要在支付、退款环节做策略判断。营销活动薅羊毛秒杀、优惠券场景下同一设备或者同一IP短时间内大量参与活动。每个场景的拦截时机不一样有的在注册时、有的在登录时、有的在支付前、有的在下单后异步审核。把时机定清楚后续技术方案的颗粒度才定得下来。1.2 技术选型之前的业务规则梳理在做任何技术选型之前我先拉着运营、风控、客服开了三天的会把规则的来源理清楚。这一步决定了整个系统的设计方向。规则来源大体分三类第一类是名单类比如黑名单手机号、黑名单IP、白名单用户。这类最简单但量很大需要支持运营同学直接在后台配置不用发版。第二类是阈值类比如“同一IP 1小时内登录失败超过5次”“同一设备24小时内注册超过3个账号”。这类规则需要实时计算频次对缓存和数据结构有要求。第三类是行为序列类比如“用户先改密码、再下单、然后立刻申请退款”这种链路。这类规则依赖业务事件的上报和聚合。规则数量一旦超过50条你就必须认真考虑“规则配置化”。我见过很多团队把规则写在代码里每次改阈值都要发版运营同学骂声一片。反欺诈平台的核心价值之一就是把规则的维护权从研发手里交还给业务人员。这里有一个非常重要的认知规则引擎不是给人秀技术用的是为了让规则“可配置、可灰度、可回滚、可审计”。这一条会贯穿后面所有的设计决策。2. 基于Spring Boot的整体架构与模块划分2.1 为什么选Spring Boot而不是自研框架技术上选型的时候团队内部是有争论的。有人提出自己写一套高性能的网关服务用Netty、不依赖Web容器把RT压到极致。这个方案性能确实强但成本很高。最后我们还是选了Spring Boot理由很务实一是团队技术栈统一招来的后端开发都具备Spring Boot能力不需要额外学习成本。反欺诈平台最怕的不是慢而是没人敢维护。二是生态成熟。Spring Boot的starter机制极大降低了接入中间件的成本。Redis、MQ、配置中心、Sentinel、Flowable都是一行依赖的事情。三是在我们的业务量级下Spring Boot配合优化完全够用。决策服务单机QPS做到2000以上完全没有问题加机器就能横向扩。性能不是靠框架拼出来的而是靠缓存设计、异步拆分和合理的降级策略。2.2 核心模块风险决策服务、名单库、案件中心、规则配置后台整个平台我拆成了四个Spring Boot应用四个应用各司其职部署上互相隔离避免一个模块出问题拖垮全部。风险决策服务是核心业务方通过HTTP接口调用入参是设备、用户、IP、场景等实时信息出参是“放行”“拒绝”或“人工审核”的决策结果。这个服务只做决策不写业务库里的核心数据。名单库是一个独立的服务负责黑名单、白名单、灰名单的管理。它提供查询接口也维护一份本地缓存保证决策链路上查询名单不会拖慢主流程。案件中心负责风控命中后的案件落库、查询、审核操作。当决策结果为“人工审核”时案件中心会生成一条风险案件推送给风控运营人员处理。规则配置后台是给运营和风控用的Web管理端。运营人员在这里配置规则、调整阈值、查看规则命中统计。配置发布后通过配置中心下发到各个决策服务节点不用重启。这四个应用全部基于Spring Boot构建但它们是不同的工程、不同的数据库实例、不同的小组维护边界非常清晰。2.3 模块间的通信与数据流模块通信的方式也是反复推敲过的总结下来就是同步只走核心链路异步解耦非核心动作。同步接口只有一个风险决策接口。业务系统在需要风控判断的时候调用它同步等待结果。这个接口的RT上限是100毫秒超过就要降级。非核心动作全部走异步决策完成后需要保存完整的请求日志和命中详情这个操作通过MQ异步落库不阻塞主链路。需要生成人工审核案件的时候也是异步通知案件中心。规则配置后台发布规则后通过配置中心推送不直接操作决策服务的本地缓存。所有模块之间不共享数据库。数据的一致性通过消息队列和幂等机制来保证不允许服务间直接读对方的表。这条原则看上去很简单但在实际开发中非常容易被突破。尤其是一开始为了图省事直接让案件中心连了决策服务的库后来发现一个慢SQL就能把决策服务拖垮教训很深刻。3. 规则引擎与特征计算的落地实现3.1 规则引擎选型自研轻量规则还是Drools规则引擎是整个平台里争议最大的一块。团队里有同事强烈建议用Drools理由是企业级、成熟、支持复杂的规则语法。我评估了几天最后还是放弃Drools选择了自研轻量规则。我不是说Drools不好它确实强大但它的强是建立在复杂的DSL和内存模型基础上的。团队里大部分人没接触过Drools学习成本高而且Drools的规则排查比较困难出了问题很难定位是哪条规则导致的。更重要的是我们的规则大部分是“特征A 条件比较 动作”根本用不到复杂的推理引擎。自研轻量规则的做法是把规则定义为一棵JSON结构树包含条件列表和动作。条件里的特征值从特征服务动态获取比较操作符支持大于、小于、等于、包含、正则等。规则的执行就是一个遍历条件树的过程逻辑非常透明。看起来很简单但它解决了两个核心问题一是规则的编辑、预览、发布都可以做成可视化界面二是规则执行过程可以逐条打印日志出问题直接看日志就能定位。这种透明性在反欺诈场景里比什么都重要。3.2 规则执行引擎的实现细节规则执行引擎我拆成了三层规则解析层、特征获取层、决策输出层。规则解析层负责将配置后台下发下来的JSON规则解析成内存中的规则对象。这里有一个性能优化的关键点规则在发布时就直接编译成可执行的结构不要在每次请求时反复解析JSON。特征获取层是引擎里最容易出性能瓶颈的一层。每条规则可能需要几十个特征如果每个特征查询一次Redis、调用一次外部服务RT根本压不住。我用了一个特征聚合器先把一次请求需要的所有特征一次性拉取再交给规则引擎计算。决策输出层的逻辑则是遍历规则集合按优先级依次执行。有一条规则命中后根据命中动作决定是“直接拒绝”“标记可疑”还是“进入人工审核”并为每个命中结果打一个风险分。这里我加了一个很实用的设计规则命中之后会把命中的规则ID、命中的条件详情、计算出的特征值全部记录下来。这些数据不仅用于审计追溯还用于后续调整规则的优先级数据积累多了之后甚至可以反向训练模型。3.3 名单库的设计与缓存方案名单库是反欺诈平台里最简单、但最不能出问题的模块。黑名单手机号判断错了可能误伤大量用户白名单放错了又等于开了后门。名单库的表结构很简单核心字段是名单类型黑/白/灰、实体类型手机号、设备ID、IP、身份证号、实体值、生效时间、过期时间、创建人。查询路径上我做了两级缓存。第一级是Caffeine本地缓存容量1万条过期时间5分钟第二级是Redis过期时间15分钟。每次查询先查本地再查Redis都没有再去数据库查并且回填到两级缓存。为什么不直接用Redis因为决策服务是一个高并发应用如果所有请求都打Redis就算Redis性能再好网络开销也在那里。本地缓存可以扛掉90%以上的名单查询流量。名单变更时通过配置中心广播一条刷新消息每个节点收到消息后主动清除对应的本地缓存。这样既保证了变更的及时性又不需要引入复杂的分布式缓存方案。4. 人工审核流程与Flowable工作流的集成4.1 为什么审核流程需要工作流引擎一开始我也犯过“简单问题复杂化”的错误。案件审核嘛不就是库里加一个状态字段从“待审核”改成“已通过”吗后来发现完全不是这样。真实的风控审核流程是这样的案件进入待审核队列风控专员初审如果命中高风险规则需要提交主管复核如果涉及资金操作还要同步给财务确认如果用户申诉还要走申诉通道。这些节点之间不是简单的一路顺序流转而是有分支、有并行、有超时处理。用状态机写这些流程代码会迅速失控。状态判断散落在各个方法里新加一个节点就要改一堆逻辑而且流程的历史记录不好追溯。这个时候我决定引入Flowable工作流引擎。Flowable本身是原生支持Spring Boot集成的而且它把流程定义、流程实例的执行、任务分配都管理起来了我只需要专注业务逻辑。4.2 Spring Boot集成Flowable的关键配置集成Flowable的过程比想象中顺利但是有一些配置细节必须处理好。Maven依赖dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version7.1.0/version /dependencyapplication.yml里的关键配置spring: datasource: url: jdbc:mysql://localhost:3306/risk_workflow username: risk_user password: ${WORKFLOW_DB_PASSWORD} flowable: async-executor-activate: true database-schema-update: true history-level: full有几个坑必须提一下。第一Flowable会自动创建ACT_开头的表这些表不要和业务表混在同一个数据库里。我单独建了一个risk_workflow库专门放流程引擎的表避免权限交叉和性能干扰。第二history-level建议设为full这样才能查询完整的历史流程记录方便在风控审计时回溯整个审核过程。如果需要控制数据量可以定期清理历史实例。第三async-executor-activate必须打开因为案件审核过程中会有异步任务比如超时自动提醒、自动转派任务如果不开启异步执行器这些任务不会触发。4.3 案件生命周期与BPMN流程的配合我的案件业务流程设计成了一张BPMN图案件生成后进入“人工初审”任务节点风控专员处理后有两种走向通过则案件关闭不通过则进入“主管复核”节点。主管复核可以批准关闭也可以驳回给初审人员重新处理。Spring Boot里启动流程的代码逻辑大概是这样的public void startReviewCase(RiskCase riskCase) { MapString, Object variables new HashMap(); variables.put(caseId, riskCase.getId()); variables.put(riskScore, riskCase.getRiskScore()); variables.put(assignee, selectReviewer(riskCase)); runtimeService.startProcessInstanceByKey(caseReview, riskCase.getId(), variables); }这里有个容易被忽视的地方流程变量里不要放太多大对象只放ID和关键的几个字段。案件的其他详细信息由查询接口实时获取避免流程变量表出现数据过大导致性能问题。审核任务的处理我封装了一层TaskService的包装类对外提供approve、reject、transfer等方法。这样业务层不会直接操作Flowable的API后续如果要把Flowable替换掉影响范围也能控制住。再补充一个经验案件状态不要完全依赖流程引擎的状态而是要在业务库中维护一个case_status字段通过Flowable的监听器同步更新。原因是查询端需要按状态快速筛选案件直接查ACT_RU_TASK表在数据量大时不方便。5. 高并发下风控链路的性能优化与降级策略5.1 热点参数缓存与本地缓存的使用反欺诈决策服务对性能要求很苛刻接口RT要求100毫秒以内。如果每次请求都做分布式计算很难扛住峰值流量。所以我把“高频查询、低频变更”的数据全部做了本地缓存。最典型的是名单库数据和规则配置。我在每个决策服务节点维护了一份Caffeine缓存CacheString, Integer ruleVersionCache Caffeine.newBuilder() .maximumSize(1_000) .expireAfterWrite(Duration.ofMinutes(10)) .build(); CacheString, RiskFeatureConfig featureConfigCache Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(Duration.ofMinutes(5)) .build();变更的时候通过配置中心广播一条“版本号变更类型”的消息各节点接收到之后主动刷新本地缓存而不是等待过期。还有一个很容易忽略的点JVM参数。给决策服务设置堆内存时我预留了足够的堆外内存给Caffeine同时开启GC日志。之前遇到过一次FullGC导致RT飙到几秒钟排查下来是本地缓存设置过大加上JVM参数不合理后来调整了缓存容量和GC策略才稳定下来。5.2 异步化与MQ削峰反欺诈决策主链路是同步的但所有附属动作都拆成了异步。决策请求进来之后主线程做的事情非常纯粹解析参数、查询特征、跑规则、返回结果。而请求日志的落库、案件生成、外部通知、数据统计全部通过MQ发送出去由下游的消费者异步处理。我用的是RocketMQ主要看重它的事务消息和顺序消息能力。事务消息用于保证“决策完成”和“日志落库”这两个动作的一致性决策成功必须记录日志否则以后审计出问题无法追溯但日志落库失败又不能影响决策结果所以用事务消息做一个最终一致性的兜底。生产环境里还要注意一个细节MQ消费失败后的重试机制。重试次数不宜过多我设置了3次超过3次就转入死信队列由人工巡检处理。死信队列这个设计特别重要否则数据丢失了都不知道。5.3 降级与熔断风控挂了不能拖垮主业务反欺诈平台是一个辅助决策系统它不能成为业务的单点故障。这条原则我在设计之初就明确写进文档了。决策接口的降级策略分两层。第一层是决策服务自身的保护。我用Sentinel对风险决策接口做了熔断限流当QPS超过阈值或RT过长时直接返回降级结果而不是让请求堆积阻塞。第二层是业务侧的兜底。业务系统在调用风控接口时必须设置超时时间不能无限等待。超时之后做成什么决策这取决于业务对风险的容忍度。交易支付场景建议默认拒绝营销活动场景建议默认放行但不计入奖励。这个取舍需要和业务方提前对齐。还有一个经验降级开关一定要做成动态配置不要通过发版去调整。我用配置中心的管理键来控制降级策略线上出现问题的时候运营同学可以在10秒内切换成“全部放行”或者“全部拒绝”模式而不需要等研发改代码。6. 踩坑复盘数据一致性、重复请求与监控告警6.1 重复通知与幂等设计第一个坑就是业务系统的重试机制把我坑惨了。业务方调用风控接口时为了提高成功率设置了超时重试。结果一个请求重试了几次风控平台生成了多条重复的决策记录案件中心也重复创建了案件。解决方案是幂等设计。风险决策接口要求请求方必须传唯一请求ID我们以请求ID作为幂等键在Redis里做去重。同一个请求ID无论来几次都只执行一次完整的决策流程后续请求直接返回第一次的决策结果。实现逻辑很简单String idempotentKey risk:idempotent: requestId; Boolean first redisTemplate.opsForValue() .setIfAbsent(idempotentKey, 1, Duration.ofMinutes(10)); if (first ! null !first) { return cachedDecision(requestId); }但这个方案的坑在于缓存时间要设置合理。如果业务方在缓存过期之后才来重试还是会出现重复决策。所以我又加了数据库唯一索引作为第二道保障在决策日志表上对requestId建唯一索引插入时冲突就查询已有结果彻底堵死重复。6.2 规则变更生效延迟问题第二个坑是规则配置和实际生效之间有时候对不上。风控运营在后台改了一条规则的阈值点击发布后配置中心推送到各节点。看起来没有问题但实际运行中某几个节点的本地缓存没有及时刷新导致这些节点还在用旧规则而其他节点已经用新规则了。同一时刻不同用户被不同的规则处理运营核对数据的时候就很懵。定位下来是缓存刷新消息在某些情况下丢失了节点收不到刷新通知本地缓存一直没过期。我的处理方案是双管齐下一是给规则配置增加版本号每次请求都带上当前规则的版本号如果配置中心返回的版本号和本地不一致强制刷新二是发布规则时采用灰度发布策略先推送到少量节点观察一段时间没有异常再推送到全量节点。这个灰度过程允许运营手动控制节奏。6.3 监控指标体系与告警配置风控平台上线初期监控这块几乎是空白的出问题的时候只能靠业务方反馈。痛定思痛我把监控指标体系搭了起来。核心指标分四类第一类是可用性指标决策接口的QPS、成功率、RT分位数。RT我重点关注P99只要P99持续超过100毫秒就要查原因。第二类是决策质量指标规则命中率、拒绝率、人工审核率、误杀率。这些指标直接反映规则配置是否合理比如拒绝率突然飙升很可能是某条规则阈值配得太严。第三类是数据链路指标MQ消费积压量、异步任务执行时长、数据库慢查询数。这些指标用于发现异步链路的问题。第四类是业务反馈指标用户申诉率、欺诈投诉量。风控平台不能只看技术指标最终要和业务结果对齐。告警方面我用Prometheus Alertmanager关键告警项包括接口成功率低于99%、P99 RT超过200毫秒持续5分钟、MQ积压超过1万条、规则命中率较昨日波动超过30%。每种告警都设置了对应的值班处理人并且要求处理完毕后在告警平台里补充处理记录。7. 从规则引擎走向模型服务可扩展的方向与我的配置经验7.1 规则引擎到模型服务的渐进升级规则引擎最大的天花板是只能识别“已知”的欺诈模式。攻击者的手段更新很快规则引擎需要不断加规则运维成本越来越高。这时候就要考虑引入机器学习模型。我的整体规划是让Spring Boot决策服务作为统一入口对外仍然提供相同的决策接口但内部增加一个模型服务调用环节。规则引擎和模型服务并行计算结果然后通过一个策略融合模块将两者的风险分做加权融合输出最终的决策结果。这个做法的好处是业务方无感知不需要改动任何调用逻辑。风控团队可以在后台对比纯粹规则和纯粹模型的准确性逐步调高模型在融合中的权重。模型服务我采用的是Python技术栈对外提供REST接口Spring Boot通过HTTP调用。调用模型服务同样要走超时和降级模型服务挂了就退化成纯规则引擎。另外特征数据通过Feature Store统一管理规则引擎和模型用的特征保持一致避免两边算出来的结果互相矛盾。7.2 Spring Boot配置管理的一些经验最后聊一下配置管理这是我调试了特别久的一块。Spring Boot本身的配置能力很强但多环境管理、敏感信息加密这些还是需要取舍。我的做法是配置文件按环境拆分application-dev.yml、application-test.yml、application-prod.yml公共配置放在application.yml里。敏感信息一律不入库、不入配置文件明文。数据库密码、Redis密码、MQ密钥统一放在环境变量或者配置中心的加密配置里。配置中心选的是Nacos它自带加密插件用起来比较方便。配置变更用配置中心的发布功能不要直接改数据库。我们的规则配置、降级开关、灰度比例全部通过Nacos管理这样每次变更都有版本记录。配置中心的SDK要设置超时和缓存。生产环境曾经遇到过Nacos短暂不可用导致服务启动失败的情况后来在客户端开启本地缓存配置中心挂掉也能用最后一份配置继续跑。这些配置管理的细节单独拿出来看都挺琐碎但在实际故障中每一个都有可能成为救命的稻草。整个反欺诈平台从立项到上线前后大概花了四个月。框架层面的东西反而不难Spring Boot把工程化的事情都解决得差不多了真正花时间的是业务梳理和无数个边缘场景的处理。这套系统上线后我们的欺诈投诉量下降了大概60%人工审核效率也提升了将近一倍。如果后面有时间我想把规则引擎和模型融合的部分再往深了写一写那块才是反欺诈平台真正拉开差距的地方。
返回列表