关于spring-cloud:阿里神器Seata集成TCC模式解决分布式事务

26次阅读

共计 7424 个字符,预计需要花费 19 分钟才能阅读完成。

大家好,我是不才陈某~

这是《Spring Cloud 进阶》第 19 篇文章,往期文章如下:

  • 五十五张图通知你微服务的灵魂摆渡者 Nacos 到底有多强?
  • openFeign 夺命连环 9 问,这谁受得了?
  • 阿里面试这样问:Nacos、Apollo、Config 配置核心如何选型?这 10 个维度通知你!
  • 阿里面试败北:5 种微服务注册核心如何选型?这几个维度通知你!
  • 阿里限流神器 Sentinel 夺命连环 17 问?
  • 比照 7 种分布式事务计划,还是偏爱阿里开源的 Seata,真香!(原理 + 实战)
  • Spring Cloud Gateway 夺命连环 10 问?
  • Spring Cloud Gateway 整合阿里 Sentinel 网关限流实战!
  • 分布式链路追踪之 Spring Cloud Sleuth 夺命连环 9 问?
  • 链路追踪自从用了 SkyWalking,睡的真香!
  • 3 本书了,7 万 + 字,10 篇文章,《Spring Cloud 进阶》根底版 PDF
  • 妹子始终没搞懂 OAuth2.0,明天整合 Spring Cloud Security 一次说明确!
  • OAuth2.0 实战!应用 JWT 令牌认证!
  • OAuth2.0 实战!玩转认证、资源服务异样自定义这些骚操作!
  • 实战干货!Spring Cloud Gateway 整合 OAuth2.0 实现分布式对立认证受权!
  • 字节面试这样问:跨库多表存在大量数据依赖问题有哪些解决方案?
  • 实战!退出登录时如何借助外力使 JWT 令牌生效?
  • 实战!Spring Cloud Gateway 集成 RBAC 权限模型实现动静权限管制!

明天这篇文章介绍一下 Seata 如何实现 TCC 事务模式,文章目录如下:

什么是 TCC 模式?

TCC(Try Confirm Cancel)计划是一种利用层面侵入业务的两阶段提交。是目前最火的一种柔性事务计划,其核心思想是:针对每个操作,都要注册一个与其对应的确认和弥补(撤销)操作

TCC 分为两个阶段,别离如下:

  • 第一阶段:Try(尝试),次要是对业务零碎做检测及资源预留 (加锁,锁住资源)
  • 第二阶段 :本阶段依据第一阶段的后果,决定是执行confirm 还是cancel

    1. Confirm(确认):执行真正的业务(执行业务,开释锁)
    2. Cancle(勾销):是预留资源的勾销(出问题,开释锁)

为了不便了解,上面以电商下单为例进行计划解析,这里把整个过程简略分为扣减库存,订单创立 2 个步骤,库存服务和订单服务别离在不同的服务器节点上。

假如商品库存为 100,购买数量为 2,这里检查和更新库存的同时,解冻用户购买数量的库存,同时创立订单,订单状态为待确认。

①Try 阶段

TCC 机制中的 Try 仅是一个初步操作,它和后续的确认一起能力真正形成一个残缺的业务逻辑,这个阶段次要实现:

  • 实现所有业务查看(一致性)。
  • 预留必须业务资源(准隔离性)。
  • Try 尝试执行业务。

②Confirm / Cancel 阶段

依据 Try 阶段服务是否全副失常执行,继续执行确认操作(Confirm)或勾销操作(Cancel)。

Confirm 和 Cancel 操作满足幂等性,如果 Confirm 或 Cancel 操作执行失败,将会一直重试直到执行实现。

Confirm:当 Try 阶段服务全副失常执行,执行确认业务逻辑操作,业务如下图:

这里应用的资源肯定是 Try 阶段预留的业务资源。在 TCC 事务机制中认为,如果在 Try 阶段能失常的预留资源,那 Confirm 肯定能残缺正确的提交。

Confirm 阶段也能够看成是对 Try 阶段的一个补充,Try+Confirm 一起组成了一个残缺的业务逻辑。

Cancel:当 Try 阶段存在服务执行失败,进入 Cancel 阶段,业务如下图:

Cancel 勾销执行,开释 Try 阶段预留的业务资源,下面的例子中,Cancel 操作会把解冻的库存开释,并更新订单状态为勾销。

以上便是 TCC 模式的全副概念,这部分内容在陈某之前的文章也是具体的介绍过:比照 7 种分布式事务计划,还是偏爱阿里开源的 Seata,真香!(原理 + 实战)

TCC 模式的三种类型?

业内理论生产中对 TCC 模式进行了扩大,总结出了如下三种类型,其实从官网的定义中无此说法,不过是企业生产中依据理论的需要衍生进去的三种计划。

1、通用型 TCC 解决方案

通用型 TCC 解决方案是最经典的 TCC 事务模型的实现,正如第一节介绍的模型,所有的从业务都参加到主业务的决策中。

实用场景

因为从业务服务是同步调用,其后果会影响到主业务服务的决策,因而通用型 TCC 分布式事务解决方案 实用于执行工夫确定且较短的业务,比方电商零碎的三个外围服务:订单服务、账户服务、库存服务。

这个三个服务要么同时胜利,要么同时失败。

当库存服务、账户服务的第二阶段调用实现后,整个分布式事务实现。

2、异步确保型 TCC 解决方案

异步确保型 TCC 解决方案的间接从业务服务是可靠消息服务,而真正的从业务服务则通过音讯服务解耦,作为音讯服务的生产端,异步地执行。

可靠消息服务须要提供 Try,Confirm,Cancel 三个接口。Try 接口预发送,只负责长久化存储音讯数据;Confirm 接口确认发送,这时才开始真正的投递音讯;Cancel 接口勾销发送,删除音讯数据。

音讯服务的音讯数据独立存储,独立伸缩,升高从业务服务与音讯零碎间的耦合,在音讯服务牢靠的前提下,实现分布式事务的最终一致性。

此解决方案尽管减少了音讯服务的保护老本,但因为音讯服务代替从业务服务实现了 TCC 接口,从业务服务不须要任何革新,接入老本非常低。

实用场景:

因为从业务服务生产音讯是一个异步的过程,执行工夫不确定,可能会导致不统一工夫窗口减少。因而,异步确保性 TCC 分布式事务解决方案只实用于对最终一致性工夫敏感度较低的一些被动型业务(从业务服务的处理结果不影响主业务服务的决策,只被动的接管主业务服务的决策后果)。比方会员注册服务和邮件发送服务:

3、弥补型 TCC 解决方案

弥补型 TCC 解决方案与通用型 TCC 解决方案的构造类似,其从业务服务也须要参加到主业务服务的流动决策当中。但不一样的是,前者的从业务服务只须要提供 Do 和 Compensate 两个接口,而后者须要提供三个接口。

Do 接口间接执行真正的残缺业务逻辑,实现业务解决,业务执行后果内部可见;Compensate 操作用于业务弥补,对消或局部对消正向业务操作的业务后果,Compensate 操作需满足幂等性。

与通用型解决方案相比,弥补型解决方案的从业务服务不须要革新原有业务逻辑,只须要额定减少一个弥补回滚逻辑即可,业务革新量较小。但要留神的是,业务在一阶段就执行残缺个业务逻辑,无奈做到无效的事务隔离,当须要回滚时,可能存在弥补失败的状况,还须要额定的异样解决机制,比方人工染指。

实用场景

因为存在回滚弥补失败的状况,弥补型 TCC 分布式事务解决方案只实用于一些并发抵触较少或者须要与内部交互的业务,这些内部业务不属于被动型业务,其执行后果会影响主业务服务的决策。

以上局部内容参考自:https://seata.io/zh-cn/blog/t…

TCC 事务模式的落地实现

在后面文章中介绍了 Seata 的 AT 模式,有不分明的能够看:比照 7 种分布式事务计划,还是偏爱阿里开源的 Seata,真香!(原理 + 实战)

当然 Seata 反对的事务模式不局限于 AT 模式,还有 TCC 模式、SAGA模式、XA模式,上面整合一下 TCC 模式。

1、演示场景

就以电商零碎中下订单为例,为了演示,间接去掉账户服务,以订单服务、库存服务为例介绍。

具体的逻辑如下:

  1. 客户端调用下订单接口
  2. 扣库存
  3. 创立订单
  4. 申请实现

依据下面的逻辑可知,订单服务必定是主业务服务,事务的发起方,库存服务是从业务服务,参加事务的决策。

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 操作胜利之后提交事务,波及到的操作如下:

  1. 开释 try 操作解冻的库存(解冻库存 - 购买数量)
  2. 生成订单

伪代码如下:

@Transactional
public boolean confirm(){
    // 开释掉 try 操作预留的库存
    cleanFrozen();
    // 批改订单,状态为已实现
    updateOrder();
    return true;
}

留神:这里如果返回 false,遵循 TCC 标准,应该要一直重试,直到 confirm 实现。

3、二阶段的 cancel 操作

cancel操作在一阶段 try 操作出现异常之后执行,用于回滚资源,波及到的操作如下:

  1. 复原解冻的库存(解冻库存 - 购买数量、库存 + 购买数量)
  2. 删除订单

伪代码如下:

@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 办法必须保障同一个全局事务中的幂等性。

解决方案

解决逻辑很简略:凑合幂等,天然是要利用幂等标识进行防重操作。

3、悬挂

事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会呈现因网络拥挤而导致的超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,Cancel 调用未超时;

在此之后,拥挤在网络上的一阶段 Try 数据包被 TCC 服务收到,呈现了二阶段 Cancel 申请比一阶段 Try 申请先执行的状况,此 TCC 服务在执行晚到的 Try 之后,将永远不会再收到二阶段的 Confirm 或者 Cancel,造成 TCC 服务悬挂。

解决方案

解决逻辑很简略:在执行 try 办法操作资源之前判断 cancel 办法是否曾经执行;同样的在 cancel 办法执行后要记录执行的状态。

4、总结

针对以上三个异样,落地的解决方案很多,比方保护一个 事务状态表,每个事务的执行阶段全副记录下来。

  • 幂等:在执行 confirm 或者 cancel 之前依据事务状态表查问以后全局事务是否曾经执行过 confirm 或者 cancel 办法
  • 空回滚:在执行 cancel 之前能力依据事务状态表查问以后全局事务是否曾经执行胜利 try 办法
  • 悬挂:在执行 try 办法之前,依据事务状态表查问以后全局事务是否曾经执行过 cancel 办法

Seata 整合 TCC 实现

对于如何搭建我的项目、增加依赖这里就不再细说了,不相熟的能够看我之前的文章:比照 7 种分布式事务计划,还是偏爱阿里开源的 Seata,真香!(原理 + 实战)

本节只介绍要害代码,毕竟篇幅无限,其余局部请自行下载源码。

案例源码已上传 GitHub,关注公号:码猿技术专栏,回复要害:9531 获取!

源码目录如下:

我的项目启动所须要的相干文件如下图:

nacos目录中的 SEATA_GROUP 是 Seata 事务服务端和客户端所须要的相干配置,间接导入 nacos 即可。

seata目录中的 conf 是 1.3.0 版本服务端的配置

SQL目录是相干的几个数据库。

1、TCC 接口定义

order-boot 模块创立 OrderTccService,代码如下:

代码中正文曾经很残缺了,上面挑几个重点介绍一下:

  1. @LocalTCC:该注解开启 TCC 事务
  2. @TwoPhaseBusinessAction:该注解标注在 try 办法上,其中的三个属性如下:

    1. name:TCC 事务的名称,必须是惟一的
    2. commitMethod:confirm 办法的名称,默认是 commit
    3. rollbackMethod:cancel 办法的名称,,默认是 rollback
  3. confirm 和 cancel 的返回值尤为重要,返回 false 则会一直的重试。

2、TCC 接口实现

定义有了,总要实现,如下:

1、try 办法

处的代码是为了避免悬挂异样,从事务日志表中获取全局事务 ID 的状态,如果是 cancel 状态则不执行。

处的代码解冻库存

处的代码生成订单,状态为待确认

处的代码向幂等工具类中增加一个标记,key 以后类 全局事务 IDvalue为以后工夫戳。

留神:必须要开启本地事务,如上代码应用 @Transactional 开启本地事务

2、confirm 办法

处的代码从幂等工具类中依据以后类和全局事务 ID 获取值,因为 try 阶段执行胜利会向其中增加值,confirm 办法执行胜利会移出这个值,因而在 confirm 结尾判断这个值是否存在就起到了幂等成果,避免重试的成果。

处的代码从幂等工具类中移出 try 办法中增加的值。

处的代码是从 BusinessActionContext 中获取 try 办法中的入参。

处的代码是开释掉解冻的库存

处的代码是批改订单的状态为已实现。

留神:1. 开启本地事务 2. 留神返回值,返回 false 时将会重试

3、cancel 办法

处的代码是向事务日志记录表中插入一条数据,标记以后事务进入 cancel 办法,用来避免悬挂,这个和 try 办法中的 处的代码相响应。

处的代码是为了避免幂等和空回滚,因为只有当 try 办法中执行胜利幂等工具类中对应的以后类和全局事务 ID 才会存储该值。这样既避免了幂等,也避免了空回滚。

处的代码复原解冻的库存。

处的代码删除这笔订单

处的代码是移出幂等工具类以后类和全局事务 ID 对应的值。

3、如何避免 TCC 模型的三个异样?

实现办法有很多,有些案例是全副应用事务日志表记录以后的状态,这样完满的解决了幂等、空回滚、悬挂的问题。

陈某这里为了不便,应用了两种计划,如下:

1、幂等、空回滚

应用了一个幂等工具类,其中是个 Map,key 为以后类和全局事务 ID,value 是工夫戳。

代码如下:

思路如下:

  1. 在 try 办法最初应用幂等工具类中的 add 办法增加值
  2. 在 confirm、cancel 办法中应用幂等工具类中的 remove 办法移出值
  3. 在 confirm、cancel 办法中应用幂等工具类中 get 办法获取值,如果为空,则示意曾经执行过了,间接返回 true,这样既避免了幂等,也避免了空回滚。

2、悬挂

悬挂的实现依附的是事务日志表,表构造如下:

CREATE TABLE `transactional_record` (`id` bigint(11) NOT NULL AUTO_INCREMENT,
  `xid` varchar(100) NOT NULL,
  `status` int(1) DEFAULT NULL COMMENT '1. try  2 commit 3 cancel',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

其中的 xid 是全局事务 ID,status 是事务的状态。

其余的字段本人能够扩大

解决悬挂问题的逻辑如下:

  1. cancel 办法中将以后全局事务 ID 记录到事务日志表中,状态为 cancel
  2. try 办法执行资源操作前查看事务日志表中以后全局事务 ID 是否曾经是 cancel 状态

4、创立订单的业务办法

下面只是实现了 TCC 的三个办法,主业务事务发起方还未提供,代码如下:

@GlobalTransactional这个注解开启了全局事务,是事务的发起方。

外部间接调用的 TCC 的 try 办法。

5、其余的配置

以上只是列出了要害的步骤,残余其余的配置本人依据案例源码欠缺,如下:

  1. 接口测试
  2. 整合 nacos
  3. 整合 feign
  4. 整合 seata,TCC 模式中的配置和 AT 模式的 Seata 配置雷同

留神:肯定要配置 Seata 的事务组tx-service-group,配置办法见之前的文章。

6、总结

TCC 事务模型相对来说比较简单的一种,有趣味的能够下载源码试试。

案例源码已上传 GitHub,关注公号:码猿技术专栏,回复要害:9531 获取!

最初说一句(别白嫖,求关注)

陈某每一篇文章都是精心输入,曾经写了 3 个专栏,整顿成PDF,获取形式如下:

  1. 《Spring Cloud 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Spring Cloud 进阶 获取!
  2. 《Spring Boot 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Spring Boot 进阶 获取!
  3. 《Mybatis 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Mybatis 进阶 获取!

正文完
 0