分布式事务
说明
Zebra 微服务框架集成了 Seata 的 TCC 模式用于支持分布式事务。
虽然 Zebra 微服务框架提供了分布式事务组件,但是仍需开发者实现各种异常处理(如空回滚、幂等、悬挂),实现一个较为完备分布式事务复杂度也还是不低,请确定是否确实要使用分布式事务。
样例
请参考 TODO
开发
引入依赖
引入如下依赖
<dependency>
<groupId>com.guosen</groupId>
<artifactId>zebra-distributed-transaction</artifactId>
<version>${zebra.version}</version>
</dependency>
配置
配置样例
zebra.distributed.transaction.service.default.grouplist=seataServerIP:seataServerPort
配置说明
配置项 | 说明 |
---|---|
zebra.distributed.transaction.service.default.grouplist | Seata Server 服务器地址信息,格式为 IP:PORT |
代码编写
RM 资源的封装
对于分布式事务用到的各种资源,比如 DB 和远程微服务调用。必须定义一个 LocalTCC 接口给包装起来。
@LocalTCC
public interface BusBookingService {
@TwoPhaseBusinessAction(name = "BusTccAction" , commitMethod = "commit", rollbackMethod = "rollback")
boolean prepare(BusinessActionContext actionContext,
@BusinessActionContextParameter(paramName = "busBooking") BusBooking busBooking);
/**
* Commit boolean.
*
* @param actionContext the action context
* @return the boolean
*/
boolean commit(BusinessActionContext actionContext);
/**
* Rollback boolean.
*
* @param actionContext the action context
* @return the boolean
*/
boolean rollback(BusinessActionContext actionContext);
}
如上图,在接口添加如下注解
- @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 函数的参数上添加此注解。
全局事务的封装
@Autowired
private BusBookingService busBookingService;
@Autowired
private HotelBookingService hotelBookingService;
/**
* 添加GlobalTransactional注解,以便开启分布式事务
* 必须在实现类上面加,不能在接口上加。
*/
@GlobalTransactional
@Override
public void booking() {
LOGGER.info("Begin booking");
String xid = RootContext.getXID();
LOGGER.info("xid is : {}", xid);
BusBooking busBooking = new BusBooking();
busBooking.setBusId("bus001");
busBooking.setUserId("user001");
HotelBooking hotelBooking = new HotelBooking();
hotelBooking.setHotelId("hotel001");
hotelBooking.setUserId("user001");
// 调用TCC各个服务的prepare方法
// TCC在prepare执行完毕后,如果全部prepare都成功,则调用对应的commit函数(一直尝试直到所有commit成功)
// 如果有部分prepare失败,则调用对应的rollback函数(一直尝试直到所有rollback成功)
BusinessActionContext actionContext = new BusinessActionContext();
busBookingService.prepare(actionContext, busBooking);
hotelBookingService.prepare(actionContext, hotelBooking);
LOGGER.info("Finish booking");
}
如上图,在实现类(注意是类而非接口)的函数添加 @GlobalTransactional。和普通 Spring 一样,使用 @Autowired 注入加了 @LocalTCC 的接口,然后在 @GlobalTransactional 注解的函数里调用它们的 prepare 函数。
TCC 会为加了 @GlobalTransactional 注解的函数的类生成代理,当此函数被调用时,开启分布式事务。
TCC 异常处理
请参考文章分布式事务 Seata TCC 模式深度解析的第三节“TCC 异常控制”。
在有了一套完备的 TCC 接口之后,是不是就真的高枕无忧了呢?答案是否定的。在微服务架构下,很有可能出现网络超时、重发,机器宕机等一系列的异常 Case。一旦遇到这些 Case,就会导致我们的分布式事务执行过程出现异常。根据蚂蚁金服内部多年的使用来看,最常见的主要是这三种异常,分别是空回滚、幂等、悬挂。
因此,TCC 接口里还需要解决这三类异常。实际上,这三类问题可以在 Seata 框架里完成,只不过我们现在的 Seata 框架还不具备,之后我们会把这些异常 Case 的处理移植到 Seata 框架里,业务就无需关注这些异常情况,专注于业务逻辑即可。
虽然业务之后无需关心,但是了解一下其内部实现机制,也能更好的排查问题……