阿里面试:订单创建失败,积分却扣了?分布式事务 TCC / Seata / Saga 到底选哪个?TCC的三个坑,90%的人答不上来!

📅 发布时间:2026/7/5 10:12:32 👁️ 浏览次数:
阿里面试:订单创建失败,积分却扣了?分布式事务 TCC / Seata / Saga 到底选哪个?TCC的三个坑,90%的人答不上来!
写在开头昨天一位 5 年经验的粉丝找我复盘阿里 P7 面试。他说前面聊 JVM 和 Redis 都很顺结果在分布式事务这一块翻车了。面试官问了一个经典的“微服务数据一致性”问题面试官“用户下单场景涉及三个服务订单服务创建订单、积分服务扣减积分、优惠券服务核销优惠券。假设积分扣了优惠券销了但最后一步订单创建失败比如主键冲突或数据库宕机。此时积分和优惠券怎么回滚你项目里用的什么方案为什么选它”他很自信的答“我们用 Seata 的 AT 模式加个注解就行……”面试官追问“Seata AT 模式在一阶段释放本地锁后如果有其他线程来读数据会不会读到脏数据如果积分服务是第三方接口TCC 还能用吗Saga 模式的幂等性怎么保证”他当场哑火。分布式事务是微服务架构中最难啃的骨头。今天 Fox 就带你拆解这几个主流方案告诉你什么时候该用“傻瓜式”的 Seata什么时候必须上“硬核”的 TCC以及何时无奈选择 Saga。一、 场景复现微服务下的“数据分裂”在单体应用时代这根本不是问题。一个Transactional注解利用数据库本地事务ACID要么全成要么全败。但在微服务架构下数据库是物理隔离的订单服务 - 连接Order_DB积分服务 - 连接Point_DB优惠券服务 - 连接Coupon_DB惨案发生积分扣减成功提交了。优惠券核销成功提交了。订单插入失败回滚了。结果用户的积分和券没了订单却没生成。这就是典型的分布式事务问题。二、 方案 ASeata (AT 模式) —— 开发者的“后悔药”Seata 是阿里开源的分布式事务框架其中AT (Auto Transaction)模式是最受欢迎的因为它是无侵入的。1. 代码示例傻瓜式开发你只需要在发起全局事务的方法通常是 OrderService上加一个GlobalTransactional注解。Service publicclass OrderService { Autowired private PointFeignClient pointClient; Autowired private CouponFeignClient couponClient; // 核心注解开启全局事务 GlobalTransactional(name create-order-tx, rollbackFor Exception.class) public void createOrder(OrderDTO order) { // 1. 扣减积分 (RPC调用) pointClient.reducePoint(order.getUserId(), 100); // 2. 核销优惠券 (RPC调用) couponClient.useCoupon(order.getCouponId()); // 3. 创建订单 (本地DB操作) // 如果这里报错Seata 会自动回滚上面两步 orderMapper.insert(order); } }2. 致命死穴脏读与全局锁Seata AT 为了性能在一阶段就释放了本地数据库锁。写隔离Seata 通过全局锁Global Lock防止脏写高并发下竞争激烈性能差。读隔离因为本地锁已释放其他没有加 Seata 注解的普通事务会读到一阶段已经修改但全局并未提交的数据脏读三、 方案 BTCC —— 高并发下的“特种兵”如果你的业务是电商核心交易链路必须请出 **TCC (Try-Confirm-Cancel)**。1. 核心原理与代码资源预留TCC 不依赖数据库事务而是把业务拆分成三个阶段。你需要在接口定义上明确这三个方法public interface PointService { // Phase 1: Try (资源预留) // 逻辑UPDATE point SET balance balance - 100, frozen frozen 100 TwoPhaseBusinessAction(name reducePoint, commitMethod confirm, rollbackMethod cancel) boolean prepareReduce(BusinessActionContext context, BusinessActionContextParameter(paramName userId) String userId, int amount); // Phase 2: Confirm (确认执行) // 逻辑UPDATE point SET frozen frozen - 100 boolean confirm(BusinessActionContext context); // Phase 2: Cancel (取消回滚) // 逻辑UPDATE point SET balance balance 100, frozen frozen - 100 boolean cancel(BusinessActionContext context); }2. 隐形成本Schema 侵入面试官问你成本别只说“写代码累”。最大的痛点是 DBA 想打你。你必须改造业务表给积分表加frozen字段给库存表加frozen_stock字段。如果是老系统改表结构风险极大。四、 方案 CSaga 模式 —— 第三方服务的“无奈之选”面试官绝杀题“如果积分服务是第三方 SaaS或者是银行接口你根本改不了数据库怎么搞 TCC”答案TCC 搞不定只能用 Saga。原理没有“预留Try”阶段直接“干Do”。流程正向操作直接调用银行扣款接口。补偿操作如果后续失败调用银行退款接口进行补偿。幂等性Saga 模式下由于网络可能超时正向操作扣款和补偿操作退款都必须支持幂等否则重试时可能导致多次扣款或多次退款。五、 终极对比一张图看懂选型建议收藏面试时直接画出这张表绝对加分维度Seata ATTCCSagaMQ 事务消息一致性弱一致存在中间状态最终一致Try阶段强一致最终一致最终一致并发性能低依赖全局锁极高锁粒度在行级高最高异步业务侵入无侵入注解即用强侵入写3个方法中等写补偿方法弱侵入表结构改造需要 Undo_Log 表需要增加冻结字段无需无需适用场景后台管理、B端业务核心交易、秒杀扣减老系统、第三方接口充值、发货、通知六、 灵魂拷问到底选哪个决策漏斗面试官问“选哪个”其实是在考你的架构权衡能力。请记住这个决策漏斗资产增加如返积分选MQ。允许延迟异步性能最好。资产扣减内部核心系统选TCC。强一致抗并发。虽然要改表结构但为了资金安全值得。资产扣减第三方/老系统选Saga。无法改表结构只能靠“补偿”。后台/非核心业务选Seata AT。开发效率优先。七、 TCC 落地的三大“地狱坑”加分项如果你选了 TCC面试官一定会问这三个词。答不上来说明你没做过。1. 空回滚Null Rollback现象Try 请求丢包没执行TC 就发起了 Cancel。解法“事务控制表”。Cancel 执行前查不到 Try 记录直接返回成功。2. 悬挂Hanging现象网络拥堵Cancel 比 Try 先到。Try 到了之后如果不拦住资金就被永久冻结了。解法Try 执行时查事务表。发现已有 Cancel 记录直接拒绝。3. 幂等Idempotency现象网络抖动Confirm/Cancel 被重试多次。解法基于全局事务 ID做唯一索引校验。写在最后分布式事务没有“银弹”。Seata AT 是给开发人员的“糖”TCC 是给架构师的“药”Saga 是无奈的“拐杖”。https://mp.weixin.qq.com/s/5DcvXbOKiqdPrwUSPr5Kjw