场景六:金融支付
前面所有场景都可以"差不多就行",唯独这里不行——钱错一分都是事故。支付系统要在"分布式事务天生慢"和"账目分毫不差"之间走钢丝,还得扛住大促的洪峰。
场景描述与业务特征
一次"扫码付款"背后,是一串跨机构的连环动作:用户账户扣钱 → 平台账户记账 → 商户账户加钱 → 银行/银联通道确认。每一步都在不同的系统甚至不同的公司里,任何一步失败,前面做过的都要"擦干净"。这就是分布式事务的主战场。
用一个比喻:普通业务像寄明信片(丢了就丢了),支付像押运现金——每一棒交接都要签字画押,丢了要能查出丢在哪一棒,还要能原路追回。
💎 资金安全 > 一切
宁可拒绝交易(可用性受损),不可算错账。这是全站唯一一个把一致性放在三角最顶端的场景。
🔗 长链路多机构
账户、营销(优惠券)、清结算、银行通道……一笔支付跨 5~10 个系统,还有外部机构的不可控延迟。
🔁 天然要求幂等
用户狂点支付按钮、网络超时重试、通道回调重复通知——同一笔钱只能动一次,幂等是支付的空气。
🔥 热点账户
大促时百万买家都在给同一个平台商户账户打钱,这一行记录成为整个链路的锁竞争焦点。
📋 一切可审计
监管要求每笔资金变动有流水、可追溯、可回放——不能只更新余额,必须记账(复式记账)。
🌊 大促脉冲叠加
支付洪峰跟随电商大促而来,既要强一致又要扛脉冲——两个最难的要求叠满。
整体运作流程
行业的核心共识:不用一个大事务锁住整条链路,而是把链路拆成多个本地事务,用"消息 + 状态机 + 对账"串起来,追求最终一致。每笔支付单像快递单一样有状态:创建 → 冻结 → 扣款 → 入账 → 成功,每一步都落库,断在哪都能续上或退回。
核心瓶颈与数据一致性挑战
🔗 分布式事务:既要又要的极限拉扯
扣款成功但优惠券核销失败怎么办?TCC(Try-Confirm-Cancel):先冻结资源(Try),全员就绪再确认(Confirm),有人失败就全体撤销(Cancel)。代价:每个服务要写三套接口,空回滚、悬挂等边界情况极其烧脑。
🔥 热点账户:一行记录挡住十万大军
百万笔支付都要更新平台商户账户余额,行锁串行化 = 全链路卡死。解法:缓冲记账(流水先落库,余额异步汇总更新)或子账户拆分(1个账户拆100个影子账户随机入账,定时归集)。
📞 回调风暴与状态悬置
银行回调可能重复到达、乱序到达、永远不来。支付单可能停在"PAYING"成为悬案。解法:回调幂等处理 + 定时任务主动查询通道补单 + 超时自动冲正。
🧮 对账差异:最后的守门员
无论链路多严谨,日切、通道抖动总会产生"我方有账对方没有"(单边账)。对账系统逐笔核对三方账单,差异进差错池人工/自动处理——支付系统的正确性,一半靠架构,一半靠对账。
优化手段、获益与代价
① 全链路幂等(防重三件套) 基石
支付单号全局唯一 + 数据库唯一索引兜底 + 状态机校验(只有 PAYING 态能变 SUCCESS)。
② 本地消息表(可靠事件模式) 最终一致
"扣款"和"待发送事件"写进同一个本地事务,后台任务轮询把事件投递到 MQ,成功才标记完成。
③ TCC 柔性事务 跨服务
余额、券、积分等多资源联动时:Try 冻结额度 → 全部成功则 Confirm → 任一失败则 Cancel 解冻。框架:Seata TCC 模式。
④ 热点账户缓冲记账 抗热点
入账请求只插入流水(无锁竞争),异步任务批量汇总流水更新余额;余额查询 = 上次快照 + 未汇总流水。
⑤ 单元化部署(异地多活) 容灾
按用户维度把流量和数据切成多个自包含"单元",每个单元独立部署在不同机房,单元内闭环处理。
多种解决方案对比与选型建议
| 分布式事务方案 | 一致性强度 | 性能 | 开发成本 | 适用场景 |
|---|---|---|---|---|
| XA / 2PC 数据库层两阶段提交 | 强一致 | 差 · 长持锁 | 低(框架代劳) | 低并发的内部金融核心(传统银行) |
| TCC Try-Confirm-Cancel | 准强一致 | 中上 | 高 · 三接口+三大坑 | 核心资金链路(余额+券+积分联动) |
| 本地消息表 业务与事件同事务 | 最终一致 | 好 | 中 | 主流程后的异步扩散(通知/清结算) |
| 事务消息 RocketMQ半消息机制 | 最终一致 | 好 | 中(依赖MQ能力) | 同本地消息表,基建有RocketMQ时更省事 |
| Saga 正向链+逆向补偿链 | 最终一致 | 好 · 无锁 | 中 · 每步写补偿 | 长流程编排(跨境支付/旅游订单) |
推演检验:换个条件还成立吗?
背下架构图不算学会,能判断"条件变了方案还成不成立"才算。先自己推,再展开参考。
推演 1:TCC 的 Try 成功了,但 Confirm 阶段一直失败(下游系统故障),能回滚 Try 当作没发生过吗?
💡 参考推演
不能。一旦进入 Confirm 阶段,语义上交易已被判定"必须成功",只能向前推进:无限重试 + 递增退避 + 告警升级人工处理。这就是为什么 Confirm/Cancel 必须设计成幂等——重试十次和一次效果相同。如果这时回滚 Try,就会出现"用户扣款成功但商家没到账"的幸存者状态。分布式事务的难点不在正常流程,在"卡在中间态时往哪边推"。
推演 2:如果业务方说"营销红包账户允许少量超发(预算内活动成本)",还需要上 TCC 吗?
💡 参考推演
不需要。一致性要求一旦从"分毫不差"降到"允许小额误差",方案可以退到 Redis 预扣 + 异步落账 + 事后对账,吞吐翻几倍、复杂度大降。关键洞察:一致性等级是业务决策,不是技术决策——同一家公司里,交易账户用 TCC、红包账户用最终一致,完全合理。把所有账户都按最高标准建设,是最常见的过度设计。
推演 3:对账系统发现一笔 3 分钱的差异,应该让系统自动冲正吗?那 3 万块的差异呢?
💡 参考推演
要分级处置:小额且差异模式已知(如"渠道回调丢失"这种见过一万次的模式)可自动冲正,但每笔都留完整审计日志;大额或未知模式必须冻结 + 人工介入——因为自动修复本身也可能出错,用错误的规则批量"修复"反而把小事故放大成大事故。对账是最后一道防线,而最后一道防线的原则是:宁可停下来问人,不可自作主张。