分布式事务

说明

Zebra 微服务框架集成了 Seata 的 TCC 模式用于支持分布式事务。

虽然 Zebra 微服务框架提供了分布式事务组件,但是仍需开发者实现各种异常处理(如空回滚、幂等、悬挂),实现一个较为完备分布式事务复杂度也还是不低,请确定是否确实要使用分布式事务。

样例

请参考 TODO

开发

引入依赖

引入如下依赖

  1. <dependency>
  2. <groupId>com.guosen</groupId>
  3. <artifactId>zebra-distributed-transaction</artifactId>
  4. <version>${zebra.version}</version>
  5. </dependency>

配置

配置样例

  1. zebra.distributed.transaction.service.default.grouplist=seataServerIP:seataServerPort

配置说明

配置项 说明
zebra.distributed.transaction.service.default.grouplist Seata Server 服务器地址信息,格式为 IP:PORT

代码编写

RM 资源的封装

对于分布式事务用到的各种资源,比如 DB 和远程微服务调用。必须定义一个 LocalTCC 接口给包装起来。

  1. @LocalTCC
  2. public interface BusBookingService {
  3. @TwoPhaseBusinessAction(name = "BusTccAction" , commitMethod = "commit", rollbackMethod = "rollback")
  4. boolean prepare(BusinessActionContext actionContext,
  5. @BusinessActionContextParameter(paramName = "busBooking") BusBooking busBooking);
  6. /**
  7. * Commit boolean.
  8. *
  9. * @param actionContext the action context
  10. * @return the boolean
  11. */
  12. boolean commit(BusinessActionContext actionContext);
  13. /**
  14. * Rollback boolean.
  15. *
  16. * @param actionContext the action context
  17. * @return the boolean
  18. */
  19. boolean rollback(BusinessActionContext actionContext);
  20. }

如上图,在接口添加如下注解

  • @LocalTCC,在接口类上添加
  • @TwoPhaseBusinessAction,在 TCC 中的 Try 函数(Java 中 try 是关键字,所以样例中函数名为 prepare)上添加,同时此注解必须设置三个属性。

    | 属性名称 | 说明 || :—- | :—- || name | TCC bean 名称,必须确保唯一 || commitMethod | TCC 中 Commit 步骤要调用的此接口的函数名称 || rollbackMethod | TCC 中 Cancel 步骤要调用的此接口的函数名称 |

    同时,commit 和 cancel 对应的函数返回类型必须为 boolean,以便 TCC 框架知道 Commit 或者 Cancel 的结果。

  • @BusinessActionContextParameter,如果后续的 commit/rollback 函数要获取到传递给 prepare 函数的参数,则在 prepare 函数的参数上添加此注解。

全局事务的封装

  1. @Autowired
  2. private BusBookingService busBookingService;
  3. @Autowired
  4. private HotelBookingService hotelBookingService;
  5. /**
  6. * 添加GlobalTransactional注解,以便开启分布式事务
  7. * 必须在实现类上面加,不能在接口上加。
  8. */
  9. @GlobalTransactional
  10. @Override
  11. public void booking() {
  12. LOGGER.info("Begin booking");
  13. String xid = RootContext.getXID();
  14. LOGGER.info("xid is : {}", xid);
  15. BusBooking busBooking = new BusBooking();
  16. busBooking.setBusId("bus001");
  17. busBooking.setUserId("user001");
  18. HotelBooking hotelBooking = new HotelBooking();
  19. hotelBooking.setHotelId("hotel001");
  20. hotelBooking.setUserId("user001");
  21. // 调用TCC各个服务的prepare方法
  22. // TCC在prepare执行完毕后,如果全部prepare都成功,则调用对应的commit函数(一直尝试直到所有commit成功)
  23. // 如果有部分prepare失败,则调用对应的rollback函数(一直尝试直到所有rollback成功)
  24. BusinessActionContext actionContext = new BusinessActionContext();
  25. busBookingService.prepare(actionContext, busBooking);
  26. hotelBookingService.prepare(actionContext, hotelBooking);
  27. LOGGER.info("Finish booking");
  28. }

如上图,在实现类(注意是而非接口)的函数添加 @GlobalTransactional。和普通 Spring 一样,使用 @Autowired 注入加了 @LocalTCC 的接口,然后在 @GlobalTransactional 注解的函数里调用它们的 prepare 函数。

TCC 会为加了 @GlobalTransactional 注解的函数的类生成代理,当此函数被调用时,开启分布式事务。

TCC 异常处理

请参考文章分布式事务 Seata TCC 模式深度解析的第三节“TCC 异常控制”。

在有了一套完备的 TCC 接口之后,是不是就真的高枕无忧了呢?答案是否定的。在微服务架构下,很有可能出现网络超时、重发,机器宕机等一系列的异常 Case。一旦遇到这些 Case,就会导致我们的分布式事务执行过程出现异常。根据蚂蚁金服内部多年的使用来看,最常见的主要是这三种异常,分别是空回滚、幂等、悬挂。

因此,TCC 接口里还需要解决这三类异常。实际上,这三类问题可以在 Seata 框架里完成,只不过我们现在的 Seata 框架还不具备,之后我们会把这些异常 Case 的处理移植到 Seata 框架里,业务就无需关注这些异常情况,专注于业务逻辑即可。

虽然业务之后无需关心,但是了解一下其内部实现机制,也能更好的排查问题……