支付系统的分布式事务实践——从业务需求到 Seata Saga 模式的落地路径

📅 2026/7/21 0:07:58
支付系统的分布式事务实践——从业务需求到 Seata Saga 模式的落地路径
支付系统的分布式事务实践——从业务需求到 Seata Saga 模式的落地路径一、支付系统的分布式事务困境一笔订单为何涉及 5 个服务2025 年 Q3团队接手了一个聚合支付系统的重构任务。这个系统连接了支付宝、微信支付、银联云闪付三条支付通道每笔交易的核心流程涉及 5 个微服务订单服务创建支付订单记录交易流水风控服务对交易做实时风险评估渠道服务调用第三方支付 API 发起扣款账务服务更新商户余额和交易明细通知服务异步通知商户交易结果在旧架构中这 5 个步骤是通过一个单体服务内的Transactional注解串行完成的。微服务化改造后每个步骤变成了远程 RPC 调用传统的事务管理机制失效。首先暴露的问题是数据不一致风控服务扣减了风控额度但渠道服务调用失败退款逻辑没有正确回滚风控额度导致用户额度被错误占用。经过一周的线上数据对账发现了 47 笔存在类似问题的订单。虽然每笔金额不大但这个问题如果不从架构层面解决随着交易量的增长会呈线性增加。二、分布式事务方案选型为什么最终选择了 Seata Saga 模式面对分布式事务的经典三选一——TCC、可靠消息最终一致性、Saga——团队做了详细的方案对比方案优点缺点TCCTry-Confirm-Cancel强一致性实时回滚侵入性强每个服务需实现三接口可靠消息RocketMQ 事务消息性能好解耦无法处理同步回滚需求Saga编排模式长事务支持补偿可定制实现复杂度较高支付场景有两个特殊约束一是用户支付的超时窗口只有 30 秒微信支付的要求延迟必须可控二是部分操作必须同步完成如扣款结果不能用异步消息替代。最终选择Seata Saga 状态机模式原因有三同步执行保证时效Saga 的每一步由状态机串行/并行编排不需要额外的消息队列中转补偿机制灵活可以为每个步骤单独定义补偿操作粒度可控Seata 生态成熟与 Spring Cloud Alibaba 集成良好社区活跃有生产案例上图展示了支付交易的状态机流转。每个状态都由一个 Saga 参与者Participant实现Saga 状态机负责编排执行和补偿回滚。三、Seata Saga 模式的核心实现Saga 模式的核心是状态机定义SML JSON和补偿逻辑。状态机定义描述了事务的每一步包括正常执行路径和异常时的补偿路径。{ Name: payment-transaction, Comment: 聚合支付交易流程, StartState: CreateOrder, Version: 1.0, States: { CreateOrder: { Type: ServiceTask, ServiceName: orderService, ServiceMethod: createOrder, CompensateState: CancelOrder, Next: RiskCheck, Input: [$.[orderRequest]], Output: {orderId: $.orderId} }, RiskCheck: { Type: ServiceTask, ServiceName: riskService, ServiceMethod: checkRisk, CompensateState: UnfreezeRiskLimit, Next: PayChannel, Input: [$.[orderId]], Output: {riskPassed: $.riskPassed} }, PayChannel: { Type: ServiceTask, ServiceName: channelService, ServiceMethod: payViaChannel, CompensateState: RefundPayment, Next: UpdateAccount, Input: [$.[orderId], $.[channelType]], Output: {transactionNo: $.transactionNo}, Retry: [ {Exceptions: [com.network.TimeoutException], Interval: [5s], MaxAttempts: 3}, {Exceptions: [com.business.InsufficientBalanceException], Next: InsufficientBalance} ] } } }Java 侧的参与者实现需要遵循 Seata 约定每个参与者方法要实现正操作 补偿操作。/** * 渠道扣款服务的 Saga 参与者实现 * 实现正操作payViaChannel和补偿操作refundViaChannel */ Service public class ChannelPaymentParticipant { private final PaymentChannelClient channelClient; private final TransactionLogRepository logRepository; public ChannelPaymentParticipant(PaymentChannelClient channelClient, TransactionLogRepository logRepository) { this.channelClient channelClient; this.logRepository logRepository; } /** * 正向操作调用第三方支付渠道发起扣款 * 返回值会被 Saga 状态机写入上下文供后续步骤使用 */ public ChannelPayResult payViaChannel(String orderId, String channelType) { try { // 记录事务日志用于后续对账和补偿依据 TransactionLog log TransactionLog.create(orderId, PAY_CHANNEL); logRepository.save(log); ChannelPayResult result channelClient.pay( orderId, ChannelType.fromCode(channelType)); if (!result.isSuccess()) { throw new PaymentException(渠道扣款失败, orderId: orderId , channelCode: result.getErrorCode()); } // 更新日志状态 log.markSuccess(result.getTransactionNo()); logRepository.save(log); return result; } catch (PaymentException e) { // 业务异常直接抛出Saga 状态机会触发补偿流程 throw e; } catch (Exception e) { // 网络/框架异常包装后抛出 throw new SagaException(渠道扣款异常, orderId: orderId, e); } } /** * 补偿操作发起退款 * 必须支持幂等——同一笔订单多次调用退款不会重复执行 */ public ChannelRefundResult refundViaChannel(String orderId, String transactionNo) { try { // 幂等校验检查是否已退款 TransactionLog existRefund logRepository .findByOrderIdAndType(orderId, REFUND_CHANNEL); if (existRefund ! null existRefund.getStatus() TransactionStatus.SUCCESS) { log.info(退款已处理跳过重复补偿, orderId: {}, orderId); return new ChannelRefundResult(true, existRefund.getRefundNo()); } ChannelRefundResult result channelClient.refund( orderId, transactionNo); if (!result.isSuccess()) { // 退款失败不阻塞补偿流程记录到人工处理队列 logRepository.save(TransactionLog.createRetryTask( orderId, REFUND_CHANNEL, transactionNo)); log.error(退款补偿失败已加入人工处理队列, orderId: {}, error: {}, orderId, result.getErrorCode()); } return result; } catch (Exception e) { log.error(退款补偿异常, orderId: {}, orderId, e); // 补偿异常不向上抛出由定时任务兜底 return new ChannelRefundResult(false, SYSTEM_ERROR); } } }以上代码体现了 Saga 模式的两个关键工程实践幂等性补偿操作必须先检查是否已执行防止重复执行导致重复退款补偿失败不阻塞补偿异常不能阻碍 Saga 状态机的流转。对于补偿失败的案例通过定时任务扫描TransactionLog中状态为RETRY_PENDING的记录进行人工介入或自动重试四、生产环境落地后的数据对比与监控体系Seata Saga 上线后我们在灰度环境做了为期两周的对照实验数据一致性上线前每日对账差异笔数平均 12 笔上线后降为 0 笔对账差异原因变为第三方渠道超时导致的双边状态不一致而非内部事务问题交易成功率从 99.82% 提升到 99.95%提升来自补偿机制自动修复了一部分因瞬时故障导致的失败P99 延迟增加了约 15msSaga 状态机编排带来的额外开销在可接受范围内监控体系围绕分布式事务的可观测性建立Saga 状态机执行轨迹追踪通过 Seata 自带的seata-saga-statemachine-designer可视化工具可以实时查看每笔交易的状态机流转路径补偿成功率监控统计TransactionLog中补偿操作的成功/失败比例设定补偿失败率超过 1% 时触发告警分布式事务超时监控监控 Saga 全局事务从开始到结束的总耗时超过 60 秒的交易做标记分析五、Seata Saga 模式的适用边界与替代方案Seata Saga 模式不是银弹。根据团队的使用经验它的适用边界是适合的场景长业务流程5~10 步、需要同步返回结果的交易、对数据一致性要求高的场景支付、订单、库存扣减不适合的场景超高并发TPS 5000 时状态机编排成为瓶颈、纯异步通知用事务消息更合适、需要人工审批的长流程Saga 不适合挂起等待当 Saga 模式过于重量级时两个轻量级替代方案值得考虑本地消息表 定时任务在数据库事务中同时写入业务数据和待发送消息用定时任务扫描未发送消息并重试。实现简单不需要引入额外中间件RocketMQ 事务消息利用 RocketMQ 的半消息机制实现生产者侧的事务保障。适合发消息即事务成功的异步场景支付系统的分布式事务没有标准答案关键在于先定义数据不一致的可容忍程度再选择匹配的方案。对于金融级交易可容忍度是零那就必须投入足够的设计和工程资源来保证。