支付系统设计
2021年"双十一",某电商平台的支付系统在0点14分发生了一次严重故障:5000个用户反映账户被重复扣款,最多的一个用户被扣了12次。
事后复盘,原因简单到可笑:支付网关在处理超时重试时,没有正确校验订单状态,导致同一个订单被提交了多次到银行通道。
这不是技术难题,这是幂等性设计的基本功没做好。
后果是:平台垫付了50万元赔款,客服团队连续加班一周处理投诉,品牌信誉损失不可估量。
【架构权衡】
支付系统是分布式系统中最考验"正确性"的场景。它不像IM系统那样追求高并发,不像秒杀系统那样追求削峰,它追求的是每一分钱都要对得上。任何一致性上的疏漏,都会变成真金白银的损失。支付系统的设计哲学是:正确性 > 性能 > 功能。
一、支付系统核心问题 🔴
1.1 四大核心问题
1.2 量化指标
1.3 面试核心问题
面试官:支付系统最核心的问题是什么?
候选人:是资金一致性。支付本质上是资金转移:从用户账户到商家账户,平台在其中扮演中介。如果扣款成功但订单失败,或者扣款两次,就会造成资金损失。
面试官:那怎么保证一致性?
候选人:核心是事务+幂等+补偿:
事务:扣款和创建支付流水在同一事务里
幂等:用订单号作为唯一键,重复请求返回原结果
补偿:最终一致性,通过对账发现和修复不一致
【面试官心理】
支付系统的追问方向通常围绕"一致性"展开。能说出"事务+幂等+补偿"的候选人,说明理解了分布式一致性的基本思路;能详细解释幂等实现的候选人,说明有实战经验;能提到"对账"和"差错处理"的候选人,说明有全局视野。
二、支付链路设计 🔴
2.1 完整支付流程
2.2 支付状态机
2.3 幂等设计
2.4 幂等的边界情况
三、资金流一致性 🟡
3.1 三户模型
3.2 账户表设计
3.3 分布式事务方案
四、渠道统一接入 🟡
4.1 渠道适配器模式
4.2 渠道异常处理
五、退款流程 🟡
5.1 退款状态机
5.2 退款幂等
六、生产避坑 🟡
6.1 支付系统的五大坑
坑1:重复扣款
坑2:状态不一致
坑3:余额并发扣减
坑4:渠道回调伪造
坑5:日终对账差异
6.2 监控指标
【架构权衡】
支付系统的设计有一个核心原则:资金不能丢,丢了就要找回来。任何设计都要以资金安全为前提,宁可牺牲性能,也要保证一致性。幂等是生命线,对账是兜底保障,事务是最后防线。三者缺一不可。
七、真实面试回放 🟡
面试官:设计一个支付系统,怎么防止用户重复支付?
候选人(老王):核心是四道防线:
第一道:前端防重复点击。按钮点击后置灰,2秒内不能重复点击。但这只是安慰性设计,可以被绕过。
第二道:后端幂等。用order_id作为唯一键,同一order_id的支付请求,要么返回已有结果,要么报错。
第三道:渠道幂等。调用支付宝/微信时,out_trade_no(就是order_id)传给渠道,渠道自己也会防重。
第四道:对账兜底。日终对账时,如果发现同一order_id有多笔支付,触发告警,人工处理。
面试官:如果后端更新了订单状态,但MQ通知下游时失败了怎么办?
老王:这个问题本质是本地事务和下游通知的一致性。
方案一:事务消息。RocketMQ支持事务消息,本地事务和MQ发送在同一事务里,MQ会回调确认是否成功,不成功就重试。
方案二:本地消息表。创建一张本地消息表,和订单更新在同一个事务里。定时任务轮询消息表,发送失败就重试。
我们用方案一,RocketMQ事务消息比较成熟。
面试官:退款时,怎么保证原路退回?
老王:退款分两种:
一是原路退回:用户用支付宝付的,退款也退回支付宝。需要记录支付时的渠道交易号(支付宝交易号),退款时带上。
二是退到余额:退到平台账户余额。这种不需要原渠道支持,但用户不一定有平台账户。
大多数场景用原路退回,因为用户心理上期望"钱从哪来到哪去"。
【面试官手记】
老王这场面试的亮点:
四道防线:前端、后端、渠道、对账,层层设防
事务消息回答到位:知道RocketMQ事务消息的回调确认机制
原路退回的回答体现了产品思维:不是技术决定业务,而是业务决定技术
这场面试属于P7级别,支付系统的设计在P7面试中出现频率很高,因为支付系统涉及资金安全,需要候选人有足够的工程意识。
继续追问方向:会问"余额并发扣减怎么解决"和"渠道回调验签怎么设计",这两个问题考验候选人对并发安全和安全设计的理解。
支付系统设计的核心是资金一致性,记住三个要点:
- 幂等是生命线:订单号唯一约束 + 渠道幂等键
- 事务保证一致性:本地事务 + 消息队列事务
- 对账是兜底保障:日终对账 + 差错处理
当你能在面试中讲清楚"怎么防止重复扣款",支付系统这关就算过了。