场景六 · 金融支付
高并发场景 · Scenario 06

场景六:金融支付

前面所有场景都可以"差不多就行",唯独这里不行——钱错一分都是事故。支付系统要在"分布式事务天生慢"和"账目分毫不差"之间走钢丝,还得扛住大促的洪峰。

极高一致性 分布式事务 对账兜底 热点账户
10万+/秒大促支付峰值 TPS
0 差错资金一致性要求
99.999%可用性目标(5个9)
T+0/T+1对账兜底周期
📑 本页目录
01

场景描述与业务特征

一次"扫码付款"背后,是一串跨机构的连环动作:用户账户扣钱 → 平台账户记账 → 商户账户加钱 → 银行/银联通道确认。每一步都在不同的系统甚至不同的公司里,任何一步失败,前面做过的都要"擦干净"。这就是分布式事务的主战场。

用一个比喻:普通业务像寄明信片(丢了就丢了),支付像押运现金——每一棒交接都要签字画押,丢了要能查出丢在哪一棒,还要能原路追回。

💎 资金安全 > 一切

宁可拒绝交易(可用性受损),不可算错账。这是全站唯一一个把一致性放在三角最顶端的场景。

🔗 长链路多机构

账户、营销(优惠券)、清结算、银行通道……一笔支付跨 5~10 个系统,还有外部机构的不可控延迟。

🔁 天然要求幂等

用户狂点支付按钮、网络超时重试、通道回调重复通知——同一笔钱只能动一次,幂等是支付的空气。

🔥 热点账户

大促时百万买家都在给同一个平台商户账户打钱,这一行记录成为整个链路的锁竞争焦点。

📋 一切可审计

监管要求每笔资金变动有流水、可追溯、可回放——不能只更新余额,必须记账(复式记账)。

🌊 大促脉冲叠加

支付洪峰跟随电商大促而来,既要强一致又要扛脉冲——两个最难的要求叠满。

02

整体运作流程

行业的核心共识:不用一个大事务锁住整条链路,而是把链路拆成多个本地事务,用"消息 + 状态机 + 对账"串起来,追求最终一致。每笔支付单像快递单一样有状态:创建 → 冻结 → 扣款 → 入账 → 成功,每一步都落库,断在哪都能续上或退回。

📱 收银台 幂等键=支付单号 防重复提交 🧾 支付单状态机 INIT→FREEZE→PAYING →SUCCESS / FAIL 每步落库 💳 账户服务 TCC:冻结→扣减→解冻 🎫 营销服务 优惠券核销(可补偿) 🏦 银行/银联通道 异步回调通知结果 超时→主动查询补单 📨 本地消息表 + MQ(可靠事件) 扣款成功事件与业务同库同事务写入,异步投递给 清结算/通知/风控,失败自动重投,天然不丢 🧮 对账系统(终极兜底) T+0小时级/T+1日终 三方账单逐笔核对,差异自动冲正 📚 复式记账 每笔资金变动 借贷双分录留痕 核心思想:大事务拆小事务,状态机记录每一步,消息保证传递,对账负责终审 "实时链路追求 99.99% 正确 + 对账兜住剩下的 0.01%" —— 这就是支付行业的最终一致性哲学
🧠
为什么不用一个大事务全锁住?跨库跨机构的强一致事务(如 2PC/XA)要求所有参与方同时锁资源等待彼此——银行通道一次响应几百毫秒,期间所有锁都攥着,吞吐量会掉到几百 TPS,还容易连环阻塞。所以支付选择"每步各自提交 + 出错补偿 + 对账终审"。
03

核心瓶颈与数据一致性挑战

🔗 分布式事务:既要又要的极限拉扯

扣款成功但优惠券核销失败怎么办?TCC(Try-Confirm-Cancel):先冻结资源(Try),全员就绪再确认(Confirm),有人失败就全体撤销(Cancel)。代价:每个服务要写三套接口,空回滚、悬挂等边界情况极其烧脑。

🔥 热点账户:一行记录挡住十万大军

百万笔支付都要更新平台商户账户余额,行锁串行化 = 全链路卡死。解法:缓冲记账(流水先落库,余额异步汇总更新)或子账户拆分(1个账户拆100个影子账户随机入账,定时归集)。

📞 回调风暴与状态悬置

银行回调可能重复到达、乱序到达、永远不来。支付单可能停在"PAYING"成为悬案。解法:回调幂等处理 + 定时任务主动查询通道补单 + 超时自动冲正。

🧮 对账差异:最后的守门员

无论链路多严谨,日切、通道抖动总会产生"我方有账对方没有"(单边账)。对账系统逐笔核对三方账单,差异进差错池人工/自动处理——支付系统的正确性,一半靠架构,一半靠对账。

💥
事故现场还原:某平台通道回调接口没做幂等,银行因网络抖动重发了一批回调,导致数千笔订单重复入账;更糟的是余额更新和流水记录不在同一事务里,差错排查花了两天。教训铁律:回调必幂等、余额与流水同事务、当天必对账
04

优化手段、获益与代价

① 全链路幂等(防重三件套) 基石

支付单号全局唯一 + 数据库唯一索引兜底 + 状态机校验(只有 PAYING 态能变 SUCCESS)。

✅ 获益:用户狂点、超时重试、回调重发通通无害化;这是支付的第一性安全。
⚠️ 代价:每个接口都要设计幂等语义,开发心智负担重;唯一索引冲突的异常分支要优雅处理。

② 本地消息表(可靠事件模式) 最终一致

"扣款"和"待发送事件"写进同一个本地事务,后台任务轮询把事件投递到 MQ,成功才标记完成。

✅ 获益:业务成功则消息必达(同事务),消息必达则下游最终一致——用最朴素的手段解决最难的问题。
⚠️ 代价:下游有秒级延迟;消息表需要定期清理;下游必须幂等(消息可能重投)。

③ TCC 柔性事务 跨服务

余额、券、积分等多资源联动时:Try 冻结额度 → 全部成功则 Confirm → 任一失败则 Cancel 解冻。框架:Seata TCC 模式。

✅ 获益:比 XA 快一个数量级(不长期持锁);隔离性好(冻结额度对外不可见)。
⚠️ 代价:每个参与方要实现三个接口;空回滚、幂等、悬挂三大坑必须处理;代码量约为普通实现的 3 倍。

④ 热点账户缓冲记账 抗热点

入账请求只插入流水(无锁竞争),异步任务批量汇总流水更新余额;余额查询 = 上次快照 + 未汇总流水。

✅ 获益:入账吞吐从行锁的几百 TPS 提升到数万 TPS;大促热点账户不再是全链路瓶颈。
⚠️ 代价:余额是"准实时"的(延迟秒级);出款方向不能这么玩(可能透支),需区分借贷方向分别处理。

⑤ 单元化部署(异地多活) 容灾

按用户维度把流量和数据切成多个自包含"单元",每个单元独立部署在不同机房,单元内闭环处理。

✅ 获益:机房级故障分钟级切流,可用性冲击 5 个 9;容量随单元数线性扩展。
⚠️ 代价:改造成本巨大(数据路由、跨单元转账、全局账务汇总都要重做);只有头部机构值得投入。
🛡️
降级策略:① 通道故障 → 自动切换备用通道(多通道路由是标配);② 营销服务超时 → 跳过优惠直接原价支付(用户可事后退差价,别挡支付主流程);③ 洪峰超限 → 排队页面("排队人数较多")而非报错,把脉冲转为可控队列。三角站位:一致性铁打不动,性能靠架构手段抠出来,可用性用排队和多通道兜住。
05

多种解决方案对比与选型建议

分布式事务方案一致性强度性能开发成本适用场景
XA / 2PC
数据库层两阶段提交
强一致差 · 长持锁低(框架代劳)低并发的内部金融核心(传统银行)
TCC
Try-Confirm-Cancel
准强一致中上高 · 三接口+三大坑核心资金链路(余额+券+积分联动)
本地消息表
业务与事件同事务
最终一致主流程后的异步扩散(通知/清结算)
事务消息
RocketMQ半消息机制
最终一致中(依赖MQ能力)同本地消息表,基建有RocketMQ时更省事
Saga
正向链+逆向补偿链
最终一致好 · 无锁中 · 每步写补偿长流程编排(跨境支付/旅游订单)
🎯
选型口诀:没有"最好的分布式事务",只有"每段链路配对的方案"——资金扣减核心段用 TCC(钱的事不含糊),成功后的扩散段用本地消息表/事务消息(通知晚点没关系),长流程编排用 Saga。无论选哪个,对账系统都是必修课而非可选项——它是支付正确性的最后一道防线,也是监管的硬要求。
06

推演检验:换个条件还成立吗?

背下架构图不算学会,能判断"条件变了方案还成不成立"才算。先自己推,再展开参考。

推演 1:TCC 的 Try 成功了,但 Confirm 阶段一直失败(下游系统故障),能回滚 Try 当作没发生过吗?

💡 参考推演

不能。一旦进入 Confirm 阶段,语义上交易已被判定"必须成功",只能向前推进:无限重试 + 递增退避 + 告警升级人工处理。这就是为什么 Confirm/Cancel 必须设计成幂等——重试十次和一次效果相同。如果这时回滚 Try,就会出现"用户扣款成功但商家没到账"的幸存者状态。分布式事务的难点不在正常流程,在"卡在中间态时往哪边推"。

推演 2:如果业务方说"营销红包账户允许少量超发(预算内活动成本)",还需要上 TCC 吗?

💡 参考推演

不需要。一致性要求一旦从"分毫不差"降到"允许小额误差",方案可以退到 Redis 预扣 + 异步落账 + 事后对账,吞吐翻几倍、复杂度大降。关键洞察:一致性等级是业务决策,不是技术决策——同一家公司里,交易账户用 TCC、红包账户用最终一致,完全合理。把所有账户都按最高标准建设,是最常见的过度设计。

推演 3:对账系统发现一笔 3 分钱的差异,应该让系统自动冲正吗?那 3 万块的差异呢?

💡 参考推演

分级处置:小额且差异模式已知(如"渠道回调丢失"这种见过一万次的模式)可自动冲正,但每笔都留完整审计日志;大额或未知模式必须冻结 + 人工介入——因为自动修复本身也可能出错,用错误的规则批量"修复"反而把小事故放大成大事故。对账是最后一道防线,而最后一道防线的原则是:宁可停下来问人,不可自作主张。