外卖霸王餐“下单-返利”分布式事务:Seata AT模式的异常处理与空回滚解决

📅 2026/7/8 13:58:48
外卖霸王餐“下单-返利”分布式事务:Seata AT模式的异常处理与空回滚解决
外卖霸王餐“下单-返利”分布式事务Seata AT模式的异常处理与空回滚解决在构建高并发的外卖霸王餐系统时“下单-返利”是核心业务场景之一。该流程涉及订单服务创建订单与用户服务发放返利需要保证数据的最终一致性。本文将深入探讨如何利用 Seata 的 AT 模式处理该业务中的异常并解决因 Try 阶段超时或异常导致的空回滚问题。业务场景与分布式事务挑战外卖霸王餐的“下单-返利”流程如下订单服务 (Order Service)接收用户下单请求创建订单记录状态为“待支付”。用户服务 (User Service)根据活动规则为用户账户增加相应的返利金额或积分。这两个操作分别属于不同的微服务拥有独立的数据库。在分布式架构下必须确保两个操作同时成功或同时失败。如果订单创建成功但返利失败会导致用户不满反之如果返利成功但订单失败则会造成平台资损。Seata 的 AT 模式提供了一种非侵入式的分布式事务解决方案但在实际应用中需要妥善处理异常和空回滚。Seata AT 模式基础实现首先在订单服务的主业务方法上使用GlobalTransactional注解开启全局事务。packagecom.jumei.bdb.order.service;importcom.jumei.bdb.order.dao.OrderDao;importcom.jumei.bdb.user.api.UserService;importio.seata.spring.annotation.GlobalTransactional;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;/** * 订单服务实现 * author baodanbao.com.cn */ServicepublicclassOrderServiceImpl{AutowiredprivateOrderDaoorderDao;AutowiredprivateUserServiceuserService;/** * 创建霸王餐订单并触发返利 * param userId 用户ID * param itemId 商品ID * return 是否创建成功 */GlobalTransactionalpublicbooleancreateOrderWithRebate(LonguserId,LongitemId){// 1. 创建订单 (分支事务1)booleanorderCreatedorderDao.createOrder(userId,itemId);if(!orderCreated){// 订单创建失败全局事务回滚thrownewRuntimeException(订单创建失败);}// 2. 调用用户服务发放返利 (分支事务2)booleanrebateGivenuserService.giveRebate(userId,calculateRebateAmount(itemId));if(!rebateGiven){// 返利失败全局事务回滚thrownewRuntimeException(返利发放失败);}returntrue;}privateBigDecimalcalculateRebateAmount(LongitemId){// 计算返利金额逻辑returnBigDecimal.TEN;}}在用户服务中通过 FeignClient 被订单服务调用。packagecom.jumei.bdb.user.service;importcom.jumei.bdb.user.dao.UserAccountDao;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;/** * 用户服务实现 * author baodanbao.com.cn */ServicepublicclassUserServiceImplimplementsUserService{AutowiredprivateUserAccountDaouserAccountDao;/** * 发放返利 * param userId 用户ID * param amount 返利金额 * return 是否成功 */TransactionalOverridepublicbooleangiveRebate(LonguserId,BigDecimalamount){// 模拟业务处理例如插入返利流水、更新用户余额returnuserAccountDao.addRebate(userId,amount);}}异常处理与空回滚问题上述代码在正常流程下可以工作但在高并发或网络波动场景下存在隐患。主要问题在于如果 Try 阶段即主业务方法执行因为超时或异常而中断Seata 会触发全局回滚。然而此时分支事务的本地方法可能尚未执行或者执行到一半。Seata 在回滚时会调用分支的 Undo Log 进行反向补偿。但如果分支事务根本没来得及写入任何数据例如RPC 调用还没到达用户服务或者用户服务方法刚进入就被中断就会产生“空回滚”。空回滚意味着没有数据需要回滚但如果处理不当可能会导致后续业务逻辑混乱。例如用户服务可能在回滚后再次收到请求或者本地事务状态不一致。解决方案悬挂与幂等性控制解决空回滚的核心思路是引入状态控制确保分支事务能够识别出自己是否处于“空回滚”状态并正确处理后续的业务请求。记录全局锁或事务状态在分支事务的数据库表中增加字段记录全局事务IDXID或事务状态。检查前置状态在分支事务业务逻辑执行前先检查是否存在对应的全局事务记录。如果存在且状态为“回滚中”或“已回滚”则直接跳过业务逻辑避免产生脏数据。幂等性设计确保分支事务的业务操作是幂等的防止因网络重试等原因导致的重复执行。在用户服务的 DAO 层我们可以通过拦截或在业务逻辑中检查全局事务状态来实现。packagecom.jumei.bdb.user.dao;importio.seata.core.context.RootContext;importorg.springframework.jdbc.core.JdbcTemplate;importorg.springframework.stereotype.Repository;importjava.math.BigDecimal;/** * 用户账户数据访问对象 * author baodanbao.com.cn */RepositorypublicclassUserAccountDao{privatefinalJdbcTemplatejdbcTemplate;publicUserAccountDao(JdbcTemplatejdbcTemplate){this.jdbcTemplatejdbcTemplate;}/** * 增加返利包含空回滚检查 * param userId 用户ID * param amount 返利金额 * return 是否成功 */publicbooleanaddRebate(LonguserId,BigDecimalamount){StringxidRootContext.getXID();// 1. 检查是否存在空回滚记录如果存在则直接返回失败或跳过if(checkIfRollbackRecordExists(xid,userId)){// 发现回滚标记直接返回防止悬挂returnfalse;}// 2. 正常业务逻辑插入返利流水更新余额// ... 业务SQL执行 ...// 3. 记录全局事务ID以便后续回滚或检查insertTransactionRecord(xid,userId,amount);returntrue;}privatebooleancheckIfRollbackRecordExists(Stringxid,LonguserId){// 查询数据库检查是否存在该XID的回滚标记// 具体实现依赖于数据库设计StringsqlSELECT COUNT(*) FROM user_rebate_log WHERE xid ? AND user_id ? AND status ROLLBACK;IntegercountjdbcTemplate.queryForObject(sql,Integer.class,xid,userId);returncount!nullcount0;}privatevoidinsertTransactionRecord(Stringxid,LonguserId,BigDecimalamount){// 插入事务记录逻辑StringsqlINSERT INTO user_rebate_log (xid, user_id, amount, status) VALUES (?, ?, ?, SUCCESS);jdbcTemplate.update(sql,xid,userId,amount);}}通过上述方式当 Seata 触发回滚时如果分支事务尚未执行回滚操作会先于业务执行到达。分支事务在执行前检查到回滚标记便会直接跳过业务逻从而避免了悬挂问题。同时通过记录 XID也保证了业务的幂等性。本文著作权归 俱美开放平台 转载请注明出处