实战!阿里神器 Seata 实现 TCC模式 解决分布式事务,真香(三)
TCC事务模式的落地实现
在前面文章中介绍了Seata的AT模式,有不清楚的可以看:对比7种分布式事务方案,还是偏爱阿里开源的Seata,真香!(原理+实战)
当然Seata支持的事务模式不局限于AT模式,还有TCC模式、SAGA模式、XA模式,下面整合一下TCC模式。
1、演示场景
就以电商系统中下订单为例,为了演示,直接去掉账户服务,以订单服务、库存服务为例介绍。
具体的逻辑如下:
- 客户端调用下订单接口
- 扣库存
- 创建订单
- 请求完成
根据上面的逻辑可知,订单服务肯定是主业务服务,事务的发起方,库存服务是从业务服务,参与事务的决策。
Seata的AT模式解决方案伪代码如下:
@GlobalTransactional
public Result<Void> createOrder(Long productId,Long num,.....){
//1、扣库存
reduceStorage();
//2、创建订单
saveOrder();
}
@GlobalTransactional这个注解用于发起一个全局事务。
但是AT模式有局限性,如下:
- 性能低,锁定资源时间太长
- 无法解决跨应用的事务
因此对于要求性能的下单接口,可以考虑使用TCC模式进行拆分成两阶段执行,这样整个流程锁定资源的时间将会变短,性能也能提高。
此时的TCC模式的拆分如下:
1、一阶段的Try操作
TCC模式中的Try阶段其实就是预留资源,在这个过程中可以将需要的商品数量的库存冻结,这样就要在库存表中维护一个冻结的库存这个字段。
伪代码如下:
@Transactional
public boolean try(){
//冻结库存
frozenStorage();
//生成订单,状态为待确认
saveOrder();
}
“注意:@Transactional开启了本地事务,只要出现了异常,本地事务将会回滚,同时执行第二阶段的cancel操作。”
2、二阶段的confirm操作
confirm操作在一阶段try操作成功之后提交事务,涉及到的操作如下:
- 释放try操作冻结的库存(冻结库存-购买数量)
- 生成订单
伪代码如下:
@Transactional
public boolean confirm(){
//释放掉try操作预留的库存
cleanFrozen();
//修改订单,状态为已完成
updateOrder();
return true;
}
“注意:这里如果返回false,遵循TCC规范,应该要不断重试,直到confirm完成。”
3、二阶段的cancel操作
cancel操作在一阶段try操作出现异常之后执行,用于回滚资源,涉及到的操作如下:
- 恢复冻结的库存(冻结库存-购买数量、库存+购买数量)
- 删除订单
伪代码如下:
@Transactional
public boolean cancel(){
//释放掉try操作预留的库存
rollbackFrozen();
//修改订单,状态为已完成
delOrder();
return true;
}
“注意:这里如果返回false,遵循TCC规范,应该要不断重试,直到cancel完成。”
2、TCC事务模型的三个异常
实现TCC事务模型涉及到的三个异常是不可避免的,实际生产中必须要规避这三大异常。
1、空回滚
定义:在未调用try方法或try方法未执行成功的情况下,就执行了cancel方法进行了回滚。
怎么理解呢?未调用try方法就执行了cancel方法,这个很容易理解,既然没有预留资源,那么肯定是不能回滚。
try方法未执行成功是什么意思?
可以看上节中的第一阶段try方法的伪代码,由于try方法开启了本地事务,一旦try方法执行过程中出现了异常,将会导致try方法的本地事务回滚(注意这里不是cancel方法回滚,而是try方法的本地事务回滚),这样其实try方法中的所有操作都将会回滚,也就没有必要调用cancel方法。
但是实际上一旦try方法抛出了异常,那么必定是要调用cancel方法进行回滚,这样就导致了空回滚。
解决方案:
解决逻辑很简单:在cancel方法执行操作之前,必须要知道try方法是否执行成功。
2、幂等性
TCC模式定义中提到:如果confirm或者cancel方法执行失败,要一直重试直到成功。
这里就涉及了幂等性,confirm和cancel方法必须保证同一个全局事务中的幂等性。
解决方案:
解决逻辑很简单:对付幂等,自然是要利用幂等标识进行防重操作。
文章转自公众号:码猿技术专栏