资讯详情

资讯详情

金融服务系统从0到1:支付、对账、风控与微服务实战解析

接手这个代号为 financial-services 的项目时团队只有四个人。老板给的需求就一句话把对公客户的开户、收单、代发、资金归集放到一个平台上。听起来像普通的业务系统改造但做过金融类项目的同学都清楚这类系统的重点根本不在功能多不多而在账平不平、钱安不安全、出了问题能不能说清来龙去脉。这篇文章把我从立项到落地的完整决策过程整理了一遍包括为什么这样选型、哪些环节最容易被坑、以及后来踩过的几个真坑希望对正在做或准备做类似金融服务的团队有点参考价值。1. 先划清楚金融服务的项目边界哪些必须做哪些坚决不做金融服务的项目一上来最怕的不是技术难而是业务边界模糊。需求方经常说以后我们还要做贷款、理财、供应链融资然后希望架构上一步到位。这种想法本身没问题但如果第一次迭代就把所有可能性都塞进系统成本会失控风险也会失控。1.1 业务边界先回答你的服务覆盖到哪一层我们当时花了整整一周做业务梳理最后把范围收敛成三个核心闭环账户管理、收单支付、资金归集。其他像信贷审批、理财代销全部划到二期物理隔离在独立服务里只预留接口位。这个取舍背后是有逻辑的。账户、收单、归集都属于交易型链路对账口径和资金流可以统一设计而信贷、理财涉及不同的期限、利率、风控模型属于完全不同的业务语义硬凑进一期只会让账务模型四不像。如果你也在做类似项目建议立项时先回答三个问题你的服务是自营还是代销资金是客户托管还是银行存管业务是实时结算还是TN结算这三个答案基本决定了系统边界和数据模型的方向。1.2 合规边界资质是一切的技术前提技术方案做得再漂亮没有对应资质也上不了线。我们在技术启动之前先做了一轮合规确认是否涉及资金池、是否需要持牌支付通道、是否需要银行存管、用户协议与隐私政策是否覆盖业务全链路。这些不是法务部门单独搞定就完事开发和产品必须知道边界在哪里。比如资金归集业务如果平台先收钱再分发给商户存在合规风险如果采用银行存管模式资金不经过平台账户技术对接方式就完全不一样。我们最终选择通过已有支付持牌机构走代收付通道并在银行侧开立备付金账户系统本身不碰商户资金。这直接影响后续的账务模型——平台侧只记录应收应付和渠道流水真正扣款、结算动作全部由通道侧完成。技术侧要做的配合是把每一笔资金指令的请求方、接收方、金额、流水号、渠道回执完整落库。因为一旦涉及到资金路径审计拿不出完整的指令链路什么问题都说不清。1.3 功能取舍MVP只做三个闭环我们的一期MVP砍掉了后台报表、商户自助配置、批量导入等非核心功能保留的是商户开户、收单下单、支付结果回调、资金归集指令、每日对账和异常告警。每个闭环必须能独立跑通、独立对账、独立排查。从实际效果看这个克制非常值得。收单闭环上线两周后发现渠道回调偶发丢失就是因为我们对支付中状态的处理不够严谨。如果没有把范围压缩到最小而是同时上线一堆边缘功能排查这个问题可能要翻几套代码才能定位。功能面越窄排障半径越小这个规律在金融服务这类高复杂度项目里尤其明显。2. 基础设施选型钱相关的系统可靠性优先于炫技金融服务系统的技术选型我的核心原则只有一条能用成熟稳定方案解决的绝不用实验性方案。团队里有人当时提议引入某个新的服务网格框架来做流量治理我拦住了。不是说新技术不好而是在资金链路上任何基础设施的隐性bug都可能被放大成资金差错这种风险不值得冒。2.1 应用架构微服务没错但不是越碎越好我们最终选了Java技术栈Spring Cloud Alibaba作为微服务底座注册中心和配置中心都用Nacos。服务划分上砍到了六个用户服务、账户服务、交易服务、通道服务、风控服务、通知服务。每个服务拆到什么粒度是有讲究的。比如账户服务和交易服务是拆开的因为账户的借贷计息规则相对稳定而交易流程改动的频率高拆开之后账户模型不会被交易需求的频繁变更影响。通道服务单独拆出来也很重要因为支付渠道随时可能增加或替换通道接口的差异需要收敛在一个服务内部不能散落到上游。网关用了Spring Cloud Gateway统一做签名校验、token鉴权和黑白名单控制。金融服务对外暴露的接口全部要求签名参数拼接后加盐做HMAC-SHA256防止请求被篡改。这个逻辑放在网关层统一处理业务服务就不用每个都写一遍。2.2 数据模型与存储账户流水表的设计直接决定对账难度存储我们用的MySQL 8.0按用户ID做分库分表中间件选了ShardingSphere。账户余额表和流水表必须分表存储Redis只做热点查询缓存余额以数据库为准。这一点怎么强调都不过分——任何时候都不能走先更新缓存再写库的路径金融数据必须以数据库为准。流水表的设计我建议参考记账凭证的思路一张流水只有一个方向。也就是说收款和付款不混在一张表里用正负号区分而是分借贷两列或者干脆用明细方向字段。这样每一笔变动都能追溯这笔钱从哪来、到哪去、对应哪个业务单号、关联了哪条渠道回执。我们还加了一个账户日终汇总表每天凌晨跑批把每个账户当天的期初余额、收入汇总、支出汇总、期末余额算好落库。对账的时候直接拿这个汇总表和渠道侧账单做维度匹配效率比逐笔比对高得多。如果你把流水表和日终汇总表一并设计好后续做余额试算平衡会非常省事。2.3 消息中间件金融场景的可靠投递怎么落地消息中间件我们用的RocketMQ看重的是事务消息和延迟消息的能力。金融链路里大量操作是异步的比如支付结果通知商户、回单生成、风控异步审核。这些场景如果用普通消息生产者发送成功但消费者处理失败消息丢了就是一笔死账。RocketMQ事务消息的方案是先发半消息本地执行完业务事务后提交确认消费者看到的是确认后的消息如果本地事务回滚半消息自动删除。我们用这个机制处理了创建代付指令的场景——先写本地指令表再发半消息给通道服务通道服务拿到消息后调渠道接口。整个过程两边的状态最终一致。延迟消息用在了支付超时自动关单上。用户下单后30分钟未支付系统自动把订单置为关闭状态。这个逻辑如果靠定时任务扫表也能做但用延迟消息能减轻数据库压力消息到了再处理对应订单命中率更高。实际用下来RocketMQ的延迟消息精度够用延迟级别的配置也足够灵活。3. 支付接入与账务一致性这块坑最多值得单独说说支付接入是金融服务项目里最容易出幺蛾子的地方。渠道文档写得再好真实联调时总有意外。我们把这块单独立项就是因为它是整个系统的腰眼子——上游连业务下游连资金中间任何一个环节出错账就对不上。3.1 渠道接入的通用流程与常见坑无论接微信、支付宝还是银联流程都差不多申请商户号或开放平台账号、配置回调地址和密钥、下载SDK或对接API、联调环境跑通、测试用例评审、上线前回归。每一步都有对应的坑。第一个坑是回调地址的配置遗漏或配错。很多渠道要求回调地址必须是公网可访问的HTTPS地址且要和申请单一致。我们有一次联调时回调收不到排查半天发现是渠道侧的沙箱环境要用单独的测试回调域名跟正式域名不是同一个值。这种信息通常藏在文档的常见问题里不仔细看根本发现不了。第二个坑是渠道侧返回码的语义不统一。有的渠道返回处理中会隔几秒收到异步通知有的则不会。如果业务方默认处理中就是成功并把订单置为已支付对账时钱没到就会暴露问题。我们当时的处理是定义了自己的支付状态机待支付、支付中、支付成功、支付失败、已关闭、已退款。渠道返回处理中统一映射为支付中不直接落成功最终以渠道对账单为准。第三坑是证书管理。银行存管、代付通道通常要求双向TLS认证证书文件有有效期。我们线上出过一次代付失败原因是某个渠道的商户证书过期了半个月而证书到期前没有告警。后来我们把所有证书的有效期都录入了统一配置提前30天发提醒提前7天发告警这个教训算是用生产事故换来的。3.2 幂等设计用唯一流水号挡住重复请求金融系统里重复支付、重复退款是最要命的问题。用户前端的支付按钮连点两次、渠道回调重复推送、重试任务重复执行任何一个环节不小心就可能导致资金重复划拨。我们的做法是给每类资金指令生成一个全局唯一的流水号在数据库落库时给流水号建唯一索引。插入时如果撞了唯一索引说明请求重复直接把已存在的那条流水返回给调用方不重新执行。这个逻辑用伪代码表示大致是这样Transactional public PaymentResult handlePay(PayRequest request) { String bizNo request.getBizNo(); try { paymentMapper.insertUnique(bizNo, request.getAmount(), request.getAccountNo()); } catch (DuplicateKeyException e) { return paymentMapper.queryByBizNo(bizNo); // 幂等返回原结果 } // 后续真正的扣款逻辑 ... }关键点在于唯一流水号必须在业务处理前插入成功才能保证并发请求只有一个执行体。如果先把业务处理完再插入流水号两个并发请求可能都处理完了再去插入时兜底已经来不及了。我们还额外做了一层状态机约束已成功的订单不允许重复执行退款已退款的订单不允许再次扣款。状态流转表里每一步都有before状态和after状态任何非法流转直接拒绝。3.3 对账与差错处理T1对账是底线每天凌晨两点定时任务会从各渠道拉取前一天的账单文件解析后和本地流水逐笔比对。比对的核心维度是渠道商户号、渠道流水号、金额、交易时间、交易状态。两边能匹配上且金额一致的进入已对平池匹配不上或金额不一致的进入差错池。差错处理分成三类本地有单、渠道无单这种情况一般是不成功的请求被本地记成了成功需要把本地状态回滚到失败本地无单、渠道有单通常是渠道回调漏配或者回调丢失需要补单两边有单但金额不一致属于严重异常直接告警到资金运营群原则上不允许自动处理。对账系统的实施要注意账单文件的分片和幂等。渠道账单可能很大几万笔甚至几十万笔我们按渠道、日期、文件名做分片每个分片的解析任务用分布式锁保证只执行一次。否则重跑一遍容易产生重复流水。这里分享一个经验对账功能不要等业务上线后再补一定要和支付功能同时上线。第一天上线的资金流水如果到第三天才能对出来差错已经发生了很多情况就说不清楚了。我们是上线前就把对账定时任务跑起来用测试环境制造差异单来验证对账逻辑等正式切量的时候对账已经连续跑了好几天。4. 风控反欺诈链路不是有了规则引擎就万事大吉很多团队对风控的认知停留在接入一个规则引擎配几条黑白名单就完事了。实际上规则只是整个风控链路里最表层的部分真正决定系统安全的是数据链路和处置闭环。4.1 先搭名单库和黑白名单再谈模型我们的风控一期没有上复杂的机器学习模型而是先把三类名单建好机构黑名单、个人黑名单、设备黑名单。名单来源包括历史坏账户、渠道方共享的欺诈名单、法院失信名单通过合规渠道获取以及业务运营手工上报的黑名单。名单服务单独拆出来底层用Redis存热数据查询接口做成高可用。所有交易在进通道前必经风控风控的第一次判断就是查名单库。命中黑名单直接拦截命中白名单如平台内部测试商户、头部核心客户走快速通道减少无谓误伤。这么做是因为在数据积累不够的阶段模型的效果很难超过经验规则而名单库是规则里置信度最高、最容易解释的一类。如果一上来就上模型业务人员面对模型分数根本不知道怎么跟商户解释风控决策就变成了黑盒子。先把名单和规则做扎实后面再渐进引入机器学习这是更稳妥的路径。4.2 实时决策链路从埋点到处置实时风控决策我们走的是埋点、采集、计算、处置四段式链路。前端在关键行为上做埋点登录、下单、支付、修改结算账户。埋点数据通过消息队列实时送到风控服务风控服务把行为抽象成事件依次经过名单检查、频率检查、额度检查、规则引擎最终输出放行、人工复核、拦截三种结果。频率检查的典型场景同设备号在短时间内关联了多个不同商户账号。正常逻辑下一个设备只属于一个商户主体如果出现一拖N的情况大概率是批量注册或养号。我们当时的规则是单设备号24小时内关联用户数超过3个就告警超过5个直接拦截拦截后需要上传额外的资质材料进行人工申诉。额度检查则针对大额交易。比如单笔金额超过商户历史均值的5倍、单日累计超过该商户近30日日均的3倍触发人工复核。这类规则看起来粗暴但非常有效。金融欺诈和小额零散交易的区别往往就体现在异常量级上阈值可以根据业务数据滚动调整避免一刀切误伤。4.3 人工复核闭环与指标监控风控不能只拦截不处理人工复核闭环必须和决策链路同步建设。我们做了个风控工作台命中复核的工单推送给运营人员运营人查看订单信息、商户资质、行为序列后做出通过、拒绝、标记可疑三个操作。所有操作留痕后续如果同一商户再出问题可以直接追溯之前是谁复核、依据是什么、查看了哪些数据。风控上线后指标跟踪同样重要。我建议至少关注四个指标拦截率、误杀率、人工复核转化率、放行后逾期/拒付率。前三个指标衡量风控本身的精准度第四个指标衡量放行决策是否靠谱。我们上线三个月后发现过于激进地调高额度阈值导致误杀率升高影响了正常商户的支付体验后来改成小额频繁放行、大额从严复核的策略才平衡了业务增长与风险敞口。5. 上线不是终点压测、灰度、故障演练一个都不能少金融服务的项目总给人一种上线即成功的错觉实际上真正的考验恰恰在上线之后。第一周的生产流量、第一次大促峰值、第一次渠道故障都会暴露你在测试环境永远测不出来的问题。5.1 全链路压测先解决支撑多少交易量这个灵魂拷问老板最爱问的一个问题是这个系统能扛多少并发这个问题的正确回答方式不是拍脑袋而是用压测数据说话。我们做了两轮压测第一轮是单服务压测把通道服务、交易服务、账户服务分别压到CPU达到70%时的QPS找到各服务的单点瓶颈第二轮是全链路压测用JMeter从网关发起真实交易请求打穿所有服务看整体吞吐量和TP99。全链路压测的场景必须覆盖三类流量正常支付、重复支付、异常退款。正常支付验证吞吐重复支付验证幂等异常退款验证状态机。压测期间我们还专门模拟了渠道回调延迟2分钟的情况看系统会不会因为等待回调而积累大量半成品订单。结果真的发现了一个连接池参数配置不当的问题——通道服务的HTTP连接池默认keep-alive时间过短渠道回调延迟一上来连接复用率下降导致新建连接的开销拖慢了整体TPS。金融系统的压测还要特别关注GC情况。资金链路如果因为Full GC导致请求大面积超时影响的不只是体验还有订单状态的不确定性。我们当时给交易服务设置了并行GC和CMS GC参数的组合调优压测后TP99从480ms降到了260ms。5.2 灰度发布资金类功能的爆炸半径控制发布是另一个高风险场景。普通业务系统发个bug最多是页面报错金融服务发个bug可能就是某类交易全部失败或者资金重复划拨。我们每次发版都走灰度发布默认先切5%流量到新版本观察15分钟确认错误率和资金差错为0后再扩大到30%、50%、100%。灰度切流的关键在于灰度维度怎么选。按用户ID哈希切流是最常见的但对金融服务来说更合理的做法是结合业务维度。我们当时的做法是灰度期间只在白名单商户通常是内部测试商户和少量友好商户范围内启用新版本这样即使出现问题影响面也限定在可控范围内。对公业务和零售业务不同几百个大商户的交易量可能占了整体90%很可能一次灰度发布无意间覆盖了核心商户出问题就是大事。资金类功能的发布比普通功能更谨慎我们还会在版本发布后跑一次上线对账专项检查由定时任务把发布前后半小时的交易流水和渠道账单做一次加急比对确保没有因为新版本逻辑改动导致对账差异。5.3 可观测性与审计出了事能自证清白金融服务系统必须做好可观测性否则出了问题只能翻日志大海捞针。我们接入了Prometheus和Grafana做监控每个服务的请求量、错误率、耗时分位数、JVM堆内存都做了看板。业务侧还加了自定义指标支付成功率、回调丢失率、对账差异笔数、风控拦截率这些业务指标比技术指标更能反映系统健康状态。审计日志单独强调一下。所有资金操作涉及的操作人、操作时间、请求参数、响应结果、被调用的渠道接口、渠道返回码全部记录在审计日志表中而且这条表只做插入不做修改。合规审计和纠纷溯源都靠它。有一次商户投诉某笔代付没有到账我们通过审计日志查到该笔请求在通道服务里因为余额不足被拒绝但通知服务没有把失败结果推送给商户原因是一条消息在消费时抛异常后进入了重试队列重试期间商户端已经超时关闭了页面。整个排查过程只用了半小时靠的就是完整链路日志。我在这个项目里最深的一个体会是金融服务项目里的每一个环节都要做好最坏的打算。支付回调会丢、渠道证书会过期、消息队列会积压、流量的突发永远超出预期。与其事后救火不如提前把账务模型、幂等设计、对账机制、风控闭环这四件事做扎实。它们不会直接产生业务亮点却是所有金融服务的承重墙。最后分享一个小技巧无论系统多完善上线后前三个月一定要安排专人值夜班关注凌晨的对账任务每天早上的第一件事不是看日报而是看昨晚对账差异池的明细。金融系统的问题大多数不会在工作时间爆发而是在跑批和结算的深夜悄无声息地出现。你能在早上9点前发现并修复它这笔业务的影响可能就只是一笔差错单拖到下午影响的就是一整天的资金效率和商户信任。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →