@GlobalTransactional @Transactional(rollbackFor = Exception.class) public void saveOrder(OrderSaveParam orderSaveParam) { // 参数校验等必要操作 // ... // 校验商品库存和上架状态 checkGoodsStatusAndStock(goodsList, goodsCountMap); // 修改库存 reduceGoodsCount(goodsCountMap); // 生成订单 saveOrder(goodsList, goodsCountMap, orderSaveParam.getAddressId()); // 删除购物车中商品 shoppingCartService.deleteShoppingCartItem(orderSaveParam.getCartItemIds(), SecurityConstants.INNER); // 异常回滚事务 int i = 1 / 0; }
分布式事务执行的 准备阶段,流程图如下Order Server 在创建订单之前,会向 Seata Server(TM) 注册全局事务,并分配事务ID,对应的控制台日志如下2023-06-17 22:07:06.479 INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [127.0.0.1:8091:36427221250506976]Order Server REST调用 Goods Server 扣减商品数量,Goods Server 在执行数据修改逻辑前会向 Seata Server 注册分支事务,执行完业务逻辑后,并不执行事务提交Order Server REST调用 ShoppingCart Server 删除购物车中的商品,ShoppingCart Server 在执行数据修改逻辑前会向 Seata Server 注册分支事务,执行完业务逻辑后,同样不执行事务提交Order Server 本地执行生成订单和其他逻辑接下来是分布式事务执行的 提交阶段,因生成订单中代码逻辑抛出异常,所以该分布式事务会回滚,OrderServer中对应日志如下2023-06-17 22:07:07.029 INFO 74703 --- [io-29009-exec-2] i.s.rm.datasource.xa.ConnectionProxyXA : 127.0.0.1:8091:36427221250506976-36427221250506978 was rollbacked2023-06-17 22:07:07.220 INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction : Suspending current transaction, xid = 127.0.0.1:8091:364272212505069762023-06-17 22:07:07.220 INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction : [127.0.0.1:8091:36427221250506976] rollback status: Rollbacked
Order Server 向 Seata Server 发送 中止请求,随后 Seata Server 向 Goods Server 和 ShoppingCart Server 发送 事务回滚请求Goods Server 和 ShoppingCart Server 收到事务回滚请求后,将各自注册的分支事务回滚,最终全局分布式事务回滚,以保证数据的一致性Goods Server 分支事务回滚对应的日志如下,可以发现分支事务的ID为全局事务ID-分支ID,并显示PhaseTwo_Rollbacked 在阶段二回滚2023-06-17 22:07:07.081 INFO 74680 --- [h_RMROLE_1_4_24] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:xid=127.0.0.1:8091:36427221250506976,branchId=36427221250506986,branchType=XA,resourceId=jdbc:mysql://127.0.0.1:3306/fy_mall_goods,applicationData=null2023-06-17 22:07:07.081 INFO 74680 --- [h_RMROLE_1_4_24] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 127.0.0.1:8091:36427221250506976 36427221250506986 jdbc:mysql://127.0.0.1:3306/fy_mall_goods2023-06-17 22:07:07.096 INFO 74680 --- [h_RMROLE_1_4_24] i.s.rm.datasource.xa.ResourceManagerXA : 127.0.0.1:8091:36427221250506976-36427221250506986 was rollbacked2023-06-17 22:07:07.096 INFO 74680 --- [h_RMROLE_1_4_24] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
注:如果有朋友想试试Seata的XA模式,可以参考示例代码仓库FangYuan33/book-spring-cloud,对应的方法入口为 /saveOrder3. 对XA的思考XA能够保持多个参与者数据相互一致,但是同时也引入了比较严重的运维问题因为如果协调者宕机,那么其中已经 准备但未提交事务 的所有参与者都会被阻塞被阻塞的根本是 锁,例如在读已提交隔离级别上,数据库事务通常会获取到待修改行数据的 行级排他锁 来防止脏写在分布式事务提交或中止前,参与者数据库不能释放这些锁,因此协调者宕机多久,这些锁就要持有多久(在没有认为干预的情况下)这些锁被持有的期间,导致其他事务不能修改这些数据(根据数据库的不同,读取操作也可能被阻塞),所以这些数据相关的业务都会被阻塞,导致应用大面积的不可用,直至 存疑事务 被解决(提交/中止)理论上,如果协调者崩溃并重新启动,它应该从日志中恢复事务的状态,并解决现存的疑虑事务,但是在实际生产中,仍然会有疑虑事务的出现(可能是事务日志被破坏)也许你可能会考虑将相关应用的数据库服务器重启,但是在2PC正确的实现中,为了 原子性 的保证,重启后也必须持有存疑事务的锁那么这样 唯一的解决方案 是让管理员手动提交还是回滚事务,这是引入运维问题的所在不过,许多XA事务的实现都有一个叫做 启发式决策 的逃生出口,允许参与者单方面决定提交或放弃一个存疑事务,而无需等待协调者的决定,但是这也正是避免灾难性情况的手段,而不是为了日常的使用,因为这种方式有可能会破坏事务的原子性所以,协调者的 高可用 是需要我们考虑的问题,它本身也是一种数据库(保存了事务的结果),需要像其他应用数据库服务一样被认真的对待巨人的肩膀《数据密集型应用系统设计》:第九章 一致性与共识百度百科:XA分布式事务之XA方案(Seata实现)浅尝分布式事务Seata官方文档MySQL 中基于 XA 实现的分布式事务还不会分布式事务,seata xa模式入门实战送上原文收录:GitHub-Enthusiasm作者:京东物流 王奕龙来源:京东云开发者社区 自猿其说Tech 转载请注明来源(图片来源网络,侵删)
0 评论