分布式事务 Seata TCC 模式深度解析 | SOFAChannel#4 直播整理

<SOFA:Channel/>,有趣实用的分布式架构频道。
本文根据 SOFAChannel#4 直播分享整理,主题:分布式事务 Seata TCC 模式深度解析。
Seata: https://github.com/seata/seata
回顾视频以及 PPT 查看地址见文末。
欢迎加入直播互动钉钉群:23127468,不错过每场直播。

sofa-channel-banner-phone-掘金.jpg

2019 年 3 月,蚂蚁金服加入分布式事务 Seata 的社区共建中,并贡献其 TCC 模式。本期是 SOFAChannel 第四期,主题:分布式事务 Seata TCC 模式深度解析,本文根据觉生的直播整理。git

你们晚上好,我是 Seata Committer 觉生,来自蚂蚁金服数据中间件团队。今天的内容主要分为如下四个部分:github

  • Seata TCC 模式的原理解析;
  • 从 TCC 的业务模型与并发控制分享如何设计一个 TCC 接口,而且适配 TCC 模型;
  • 如何控制异常;
  • 性能优化,使得 TCC 模式可以知足更高的业务需求。

一、 Seata 的 TCC 模式

1.1 服务化拆分

下面咱们就进入第一个主题,Seata 的 TCC 模式。蚂蚁金服早期是单系统架构,全部业务服务几乎都在少数几个系统中。随着业务的发展,业务愈来愈复杂,服务之间的耦合度也愈来愈高,故咱们对系统进行了重构,服务按照功能进行解耦和垂直拆分。拆分以后所带来的问题就是一个业务活动原来只须要调用一个服务就能完成,如今须要调用多个服务才能完成,而网络、机器等不可靠,数据一致性的问题很容易出现,与可扩展性、高可用容灾等要求并肩成为金融 IT 架构支撑业务转型升级的最大挑战之一。数据库

从图中能够看到,从单系统到微服务转变,实际上是一个资源横向扩展的过程,资源的横向扩展是指当单台机器达到资源性能瓶颈,没法知足业务增加需求时,就须要横向扩展资源,造成集群。经过横向扩展资源,提高非热点数据的并发性能,这对于大致量的互联网产品来讲,是相当重要的。服务的拆分,也能够认为是资源的横向扩展,只不过方向不一样而已。性能优化

资源横向扩展可能沿着两个方向发展,包括业务拆分和数据分片:网络

  • 业务拆分。根据功能对数据进行分组,并将不一样的微服务分布在多个不一样的数据库上,这实际上就是 SOA 架构下的服务化。业务拆分就是把业务逻辑从一个单系统拆分到多个微服务中。
  • 数据分片。在微服务内部将数据拆分到多个数据库上,为横向扩展增长一个新的维度。数据分片就是把一个微服务下的单个 DB 拆分红多个 DB,具有一个 Sharding 的功能。经过这样的拆解,至关于一种资源的横向扩展,从而使得整个架构能够承载更高的吞吐。

横向扩展的两种方法能够同时进行运用:交易、支付与帐务三个不一样微服务能够存储在不一样的数据库中。另外,每一个微服务内根据其业务量能够再拆分到多个数据库中,各微服务能够相互独立地进行扩展。架构

Seata 关注的就是微服务架构下的数据一致性问题,是一整套的分布式事务解决方案。Seata 框架包含两种模式,一种是 AT 模式。AT 模式主要从数据分片的角度,关注多 DB 访问的数据一致性,固然也包括多服务下的多 DB 数据访问一致性问题。并发

另一个就是 TCC 模式,TCC 模式主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题,保证读资源访问的事务属性。框架

今天咱们主要讲的就是TCC模式。在讲 TCC 以前,咱们先回顾一下 AT 模式,这样有助于咱们理解后面的 TCC 模式。异步

1.2. AT 模式

对于 AT 模式,以前其余同窗已经分享过不少次,你们也应该比较熟悉了。AT 模式下,把每一个数据库被当作是一个 Resource,Seata 里称为 DataSource Resource。业务经过 JDBC 标准接口访问数据库资源时,Seata 框架会对全部请求进行拦截,作一些操做。每一个本地事务提交时,Seata RM(Resource Manager,资源管理器) 都会向 TC(Transaction Coordinator,事务协调器) 注册一个分支事务。当请求链路调用完成后,发起方通知 TC 提交或回滚分布式事务,进入二阶段调用流程。此时,TC 会根据以前注册的分支事务回调到对应参与者去执行对应资源的第二阶段。TC 是怎么找到分支事务与资源的对应关系呢?每一个资源都有一个全局惟一的资源 ID,而且在初始化时用该 ID 向 TC 注册资源。在运行时,每一个分支事务的注册都会带上其资源 ID。这样 TC 就能在二阶段调用时正确找到对应的资源。分布式

这就是咱们的 AT 模式。简单总结一下,就是把每一个数据库当作一个 Resource,在本地事务提交时会去注册一个分支事务。

1.3 TCC 模式

那么对应到 TCC 模式里,也是同样的,Seata 框架把每组 TCC 接口当作一个 Resource,称为 TCC Resource。这套 TCC 接口能够是 RPC,也以是服务内 JVM 调用。在业务启动时,Seata 框架会自动扫描识别到 TCC 接口的调用方和发布方。若是是 RPC 的话,就是 sofa:reference、sofa:service、dubbo:reference、dubbo:service 等。

扫描到 TCC 接口的调用方和发布方以后。若是是发布方,会在业务启动时向 TC 注册 TCC Resource,与DataSource Resource 同样,每一个资源也会带有一个资源 ID。

若是是调用方,Seata 框架会给调用方加上切面,与 AT 模式同样,在运行时,该切面会拦截全部对 TCC 接口的调用。每调用一次 Try 接口,切面会先向 TC 注册一个分支事务,而后才去执行原来的 RPC 调用。当请求链路调用完成后,TC 经过分支事务的资源ID回调到正确的参与者去执行对应 TCC 资源的 Confirm 或 Cancel 方法。

在讲完了整个框架模型之后,你们可能会问 TCC 三个接口怎么实现。由于框架自己很简单,主要是扫描 TCC 接口,注册资源,拦截接口调用,注册分支事务,最后回调二阶段接口。最核心的其实是 TCC 接口的实现逻辑。下面我将根据蚂蚁金服内部多年的实践来为你们分析怎么实现一个完备的 TCC 接口。

二、 TCC 业务模型与并发控制

2.1 TCC 设计原则

从 TCC 模型的框架能够发现,TCC 模型的核心在于 TCC 接口的设计。用户在接入 TCC 时,大部分工做都集中在如何实现 TCC 服务上。下面我会分享蚂蚁金服内多年的 TCC 应用实践以及在 TCC 设计和实现过程当中的注意事项。

设计一套 TCC 接口最重要的是什么?主要有两点,第一点,须要将操做分红两阶段完成。TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,其特征在于它不依赖 RM 对分布式事务的支持,而是经过对业务逻辑的分解来实现分布式事务。

TCC 模型认为对于业务系统中一个特定的业务逻辑 ,其对外提供服务时,必须接受一些不肯定性,即对业务逻辑初步操做的调用仅是一个临时性操做,调用它的主业务服务保留了后续的取消权。若是主业务服务认为全局事务应该回滚,它会要求取消以前的临时性操做,这就对应从业务服务的取消操做。而当主业务服务认为全局事务应该提交时,它会放弃以前临时性操做的取消权,这对应从业务服务的确认操做。每个初步操做,最终都会被确认或取消。所以,针对一个具体的业务服务,TCC 分布式事务模型须要业务系统提供三段业务逻辑:

1.初步操做 Try:完成全部业务检查,预留必须的业务资源。

2.确认操做 Confirm:真正执行的业务逻辑,不作任何业务检查,只使用 Try 阶段预留的业务资源。所以,只要 Try 操做成功,Confirm 必须能成功。另外,Confirm 操做需知足幂等性,保证一笔分布式事务能且只能成功一次。

3.取消操做 Cancel:释放 Try 阶段预留的业务资源。一样的,Cancel 操做也须要知足幂等性。

第二点,就是要根据自身的业务模型控制并发,这个对应 ACID 中的隔离性。后面会详细讲到。

2.2 帐务系统模型设计

下面咱们以金融核心链路里的帐务服务来分析一下。首先一个最简化的帐务模型就是图中所列,每一个用户或商户有一个帐户及其可用余额。而后,分析下帐务服务的全部业务逻辑操做,不管是交易、充值、转帐、退款等,均可以认为是对帐户的加钱与扣钱。

幻灯片10.JPG

所以,咱们能够把帐务系统拆分红两套 TCC 接口,即两个 TCC Resource,一个是加钱 TCC 接口,一个是扣钱 TCC 接口。

那这两套接口分别须要作什么事情呢?如何将其分红两个阶段完成?下面将会举例说明 TCC 业务模式的设计过程,并逐渐优化。

咱们先来看扣钱的 TCC 资源怎么实现。场景为 A 转帐 30 元给 B。帐户 A 的余额中有 100 元,须要扣除其中 30 元。这里的余额就是所谓的业务资源,按照前面提到的原则,在第一阶段须要检查并预留业务资源,所以,咱们在扣钱 TCC 资源的 Try 接口里先检查 A 帐户余额是否足够,而后预留余额里的业务资源,即扣除 30 元。

在 Confirm 接口,因为业务资源已经在 Try 接口里扣除掉了,那么在第二阶段的 Confirm 接口里,能够什么都不用作。而在 Cancel 接口里,则须要把 Try 接口里扣除掉的 30 元还给帐户。这是一个比较简单的扣钱 TCC 资源的实现,后面会继续优化它。

而在加钱的 TCC 资源里。在第一阶段 Try 接口里不能直接给帐户加钱,若是这个时候给帐户增长了可用余额,那么在一阶段执行完后,帐户里的钱就能够被使用了。可是一阶段执行完之后,有多是要回滚的。所以,真正加钱的动做须要放在 Confirm 接口里。对于加钱这个动做,第一阶段 Try 接口里不须要预留任何资源,能够设计为空操做。那相应的,Cancel 接口没有资源须要释放,也是一个空操做。只有真正须要提交时,再在 Confirm 接口里给帐户增长可用余额。

这就是一个最简单的扣钱和加钱的 TCC 资源的设计。在扣钱 TCC 资源里,Try 接口预留资源扣除余额,Confirm 接口空操做,Cancel 接口释放资源,增长余额。在加钱 TCC 资源里,Try 接口无需预留资源,空操做;Confirm 接口直接增长余额;Cancel 接口无需释放资源,空操做。

2.3 帐务系统模型并发控制

以前提到,设计一套 TCC 接口须要有两点,一点是须要拆分业务逻辑成两阶段完成。这个咱们已经介绍了。另一点是要根据自身的业务模型控制并发。

Seata 框架自己仅提供两阶段原子提交协议,保证分布式事务原子性。事务的隔离须要交给业务逻辑来实现。隔离的本质就是控制并发,防止并发事务操做相同资源而引发的结果错乱。

举个例子,好比金融行业里管理用户资金,当用户发起交易时,通常会先检查用户资金,若是资金充足,则扣除相应交易金额,增长卖家资金,完成交易。若是没有事务隔离,用户同时发起两笔交易,两笔交易的检查都认为资金充足,实际上却只够支付一笔交易,结果两笔交易都支付成功,致使资损。

能够发现,并发控制是业务逻辑执行正确的保证,可是像两阶段锁这样的并发访问控制技术要求一直持有数据库资源锁直到整个事务执行结束,特别是在分布式事务架构下,要求持有锁到分布式事务第二阶段执行结束,也就是说,分布式事务会加长资源锁的持有时间,致使并发性能进一步降低。

所以,TCC 模型的隔离性思想就是经过业务的改造,在第一阶段结束以后,从底层数据库资源层面的加锁过渡为上层业务层面的加锁,从而释放底层数据库锁资源,放宽分布式事务锁协议,将锁的粒度降到最低,以最大限度提升业务并发性能。

仍是以上面的例子举例,“帐户 A 上有 100 元,事务 T1 要扣除其中的 30 元,事务 T2 也要扣除 30 元,出现并发”。在第一阶段 Try 操做中,须要先利用数据库资源层面的加锁,检查帐户可用余额,若是余额充足,则预留业务资源,扣除本次交易金额,一阶段结束后,虽然数据库层面资源锁被释放了,但这笔资金被业务隔离,不容许除本事务以外的其它并发事务动用。

并发的事务 T2 在事务 T1 一阶段接口结束释放了数据库层面的资源锁之后,就能够继续操做,跟事务 T1 同样,加锁,检查余额,扣除交易金额。

事务 T1 和 T2 分别扣除的那一部分资金,相互之间无干扰。这样在分布式事务的二阶段,不管 T1 是提交仍是回滚,都不会对 T2 产生影响,这样 T1 和 T2 能够在同一个帐户上并发执行。

你们能够感觉下,一阶段结束之后,实际上采用业务加锁的方式,隔离帐户资金,在第一阶段结束后直接释放底层资源锁,该用户和卖家的其余交易均可以马上并发执行,而不用等到整个分布式事务结束,能够得到更高的并发交易能力。

这里稍微有点抽象,下面咱们将会针对业务模型进行优化,你们能够更直观的感觉业务加锁的思想。

2.4 帐务系统模型优化

前面的模型你们确定会想,为啥一阶段就把钱扣除了?是的。以前只是为了简单说明 TCC 模型的设计思想。在实际中,为了更好的用户体验,在第一阶段,通常不会直接把帐户的余额扣除,而是冻结,这样给用户展现的时候,就能够很清晰的知道,哪些是可用余额,哪些是冻结金额。

那业务模型变成什么样了呢?如图所示,须要在业务模型中增长冻结金额字段,用来表示帐户有多少金额处以冻结状态。

既然业务模型发生了变化,那扣钱和加钱的 TCC 接口也应该相应的调整。仍是之前面的例子来讲明。

在扣钱的 TCC 资源里。Try 接口再也不是直接扣除帐户的可用余额,而是真正的预留资源,冻结部分可用余额,即减小可用余额,增长冻结金额。Confirm 接口也再也不是空操做,而是使用 Try 接口预留的业务资源,即将该部分冻结金额扣除;最后在 Cancel 接口里,就是释放预留资源,把 Try 接口的冻结金额扣除,增长帐户可用余额。加钱的 TCC资源因为不涉及冻结金额的使用,因此无需更改。

经过这样的优化,能够更直观的感觉到 TCC 接口的预留资源、使用资源、释放资源的过程。

那并发控制又变成什么样了呢?跟前面大部分相似,在事务 T1 的第一阶段 Try 操做中,先锁定帐户,检查帐户可用余额,若是余额充足,则预留业务资源,减小可用余额,增长冻结金额。并发的事务 T2 相似,加锁,检查余额,减小可用余额金额,增长冻结金额。

这里能够发现,事务 T1 和T2 在一阶段执行完成后,都释放了数据库层面的资源锁,可是在各自二阶段的时候,相互之间并没有干扰,各自使用本事务内第一阶段 Try 接口内冻结金额便可。这里你们就能够直观感觉到,在每一个事务的第一阶段,先经过数据库层面的资源锁,预留业务资源,即冻结金额。虽然在一阶段结束之后,数据库层面的资源锁被释放了,可是第二阶段的执行并不会被干扰,这是由于数据库层面资源锁释放之后经过业务隔离的方式为这部分资源加锁,不容许除本事务以外的其它并发事务动用,从而保证该事务的第二阶段可以正确顺利的执行。

经过这两个例子,为你们讲解了怎么去设计一套完备的 TCC 接口。最主要的有两点,一点是将业务逻辑拆分红两个阶段完成,即 Try、Confirm、Cancel 接口。其中 Try 接口检查资源、预留资源、Confirm 使用资源、Cancel 接口释放预留资源。另一点就是并发控制,采用数据库锁与业务加锁的方式结合。因为业务加锁的特性不影响性能,所以,尽量下降数据库锁粒度,过渡为业务加锁,从而提升业务并发能力。

三、 TCC 异常控制

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

所以,TCC 接口里还须要解决这三类异常。实际上,这三类问题能够在 Seata 框架里完成,只不过咱们如今的 Seata框架还不具有,以后咱们会把这些异常 Case 的处理移植到 Seata 框架里,业务就无需关注这些异常状况,专一于业务逻辑便可。

虽然业务以后无需关心,可是了解一下其内部实现机制,也能更好的排查问题。下面我将为你们一一讲解这三类异常出现的缘由以及对应的解决方案。

3.1 空回滚

首先是空回滚。什么是空回滚?空回滚就是对于一个分布式事务,在没有调用 TCC 资源 Try 方法的状况下,调用了二阶段的 Cancel 方法,Cancel 方法须要识别出这是一个空回滚,而后直接返回成功。

什么样的情形会形成空回滚呢?能够看图中的第 2 步,前面讲过,注册分支事务是在调用 RPC 时,Seata 框架的切面会拦截到该次调用请求,先向 TC 注册一个分支事务,而后才去执行 RPC 调用逻辑。若是 RPC 调用逻辑有问题,好比调用方机器宕机、网络异常,都会形成 RPC 调用失败,即未执行 Try 方法。可是分布式事务已经开启了,须要推动到终态,所以,TC 会回调参与者二阶段 Cancel 接口,从而造成空回滚。

幻灯片23.JPG

那会不会有空提交呢?理论上来讲不会的,若是调用方宕机,那分布式事务默认是回滚的。若是是网络异常,那 RPC 调用失败,发起方应该通知 TC 回滚分布式事务,这里能够看出为何是理论上的,就是说发起方能够在 RPC 调用失败的状况下依然通知 TC 提交,这时就会发生空提交,这种状况要么是编码问题,要么开发同窗明确知道须要这样作。

那怎么解决空回滚呢?前面提到,Cancel 要识别出空回滚,直接返回成功。那关键就是要识别出这个空回滚。思路很简单就是须要知道一阶段是否执行,若是执行了,那就是正常回滚;若是没执行,那就是空回滚。所以,须要一张额外的事务控制表,其中有分布式事务 ID 和分支事务 ID,第一阶段 Try 方法里会插入一条记录,表示一阶段执行了。Cancel 接口里读取该记录,若是该记录存在,则正常回滚;若是该记录不存在,则是空回滚。

3.2 幂等

接下来是幂等。幂等就是对于同一个分布式事务的同一个分支事务,重复去调用该分支事务的第二阶段接口,所以,要求 TCC 的二阶段 Confirm 和 Cancel 接口保证幂等,不会重复使用或者释放资源。若是幂等控制没有作好,颇有可能致使资损等严重问题。

什么样的情形会形成重复提交或回滚?从图中能够看到,提交或回滚是一次 TC 到参与者的网络调用。所以,网络故障、参与者宕机等都有可能形成参与者 TCC 资源实际执行了二阶段防范,可是 TC 没有收到返回结果的状况,这时,TC 就会重复调用,直至调用成功,整个分布式事务结束。

幻灯片25.JPG

怎么解决重复执行的幂等问题呢?一个简单的思路就是记录每一个分支事务的执行状态。在执行前状态,若是已执行,那就再也不执行;不然,正常执行。前面在讲空回滚的时候,已经有一张事务控制表了,事务控制表的每条记录关联一个分支事务,那咱们彻底能够在这张事务控制表上加一个状态字段,用来记录每一个分支事务的执行状态。

幻灯片26.JPG

如图所示,该状态字段有三个值,分别是初始化、已提交、已回滚。Try 方法插入时,是初始化状态。二阶段 Confirm 和 Cancel 方法执行后修改成已提交或已回滚状态。当重复调用二阶段接口时,先获取该事务控制表对应记录,检查状态,若是已执行,则直接返回成功;不然正常执行。

3.3 悬挂

最后是防悬挂。按照惯例,我们来先讲讲什么是悬挂。悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。由于容许空回滚的缘由,Cancel 接口认为 Try 接口没执行,空回滚直接返回成功,对于 Seata 框架来讲,认为分布式事务的二阶段接口已经执行成功,整个分布式事务就结束了。可是这以后 Try 方法才真正开始执行,预留业务资源,前面提到事务并发控制的业务加锁,对于一个 Try 方法预留的业务资源,只有该分布式事务才能使用,然而 Seata 框架认为该分布式事务已经结束,也就是说,当出现这种状况时,该分布式事务第一阶段预留的业务资源就再也没有人可以处理了,对于这种状况,咱们就称为悬挂,即业务资源预留后无法继续处理。

什么样的状况会形成悬挂呢?按照前面所讲,在 RPC 调用时,先注册分支事务,再执行 RPC 调用,若是此时 RPC 调用的网络发生拥堵,一般 RPC 调用是有超时时间的,RPC 超时之后,发起方就会通知 TC 回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者,真正执行,从而形成悬挂。

怎么实现才能作到防悬挂呢?根据悬挂出现的条件先来分析下,悬挂是指二阶段 Cancel 执行完后,一阶段才执行。也就是说,为了不悬挂,若是二阶段执行完成,那一阶段就不能再继续执行。所以,当一阶段执行时,须要先检查二阶段是否已经执行完成,若是已经执行,则一阶段再也不执行;不然能够正常执行。那怎么检查二阶段是否已经执行呢?你们是否想到了刚才解决空回滚和幂等时用到的事务控制表,能够在二阶段执行时插入一条事务控制记录,状态为已回滚,这样当一阶段执行时,先读取该记录,若是记录存在,就认为二阶段已经执行;不然二阶段没执行。

3.3 异常控制实现

在分析完空回滚、幂等、悬挂等异常 Case 的成因以及解决方案之后,下面咱们就综合起来考虑,一个 TCC 接口如何完整的解决这三个问题。

首先是 Try 方法。结合前面讲到空回滚和悬挂异常,Try 方法主要须要考虑两个问题,一个是 Try 方法须要可以告诉二阶段接口,已经预留业务资源成功。第二个是须要检查第二阶段是否已经执行完成,若是已完成,则再也不执行。所以,Try 方法的逻辑能够如图所示:

幻灯片28.JPG

先插入事务控制表记录,若是插入成功,说明第二阶段尚未执行,能够继续执行第一阶段。若是插入失败,则说明第二阶段已经执行或正在执行,则抛出异常,终止便可。

接下来是 Confirm 方法。由于 Confirm 方法不容许空回滚,也就是说,Confirm 方法必定要在 Try 方法以后执行。所以,Confirm 方法只须要关注重复提交的问题。能够先锁定事务记录,若是事务记录为空,则说明是一个空提交,不容许,终止执行。若是事务记录不为空,则继续检查状态是否为初始化,若是是,则说明一阶段正确执行,那二阶段正常执行便可。若是状态是已提交,则认为是重复提交,直接返回成功便可;若是状态是已回滚,也是一个异常,一个已回滚的事务,不能从新提交,须要可以拦截到这种异常状况,并报警。

最后是 Cancel 方法。由于 Cancel 方法容许空回滚,而且要在先执行的状况下,让 Try 方法感知到 Cancel 已经执行,因此和 Confirm 方法略有不一样。首先依然是锁定事务记录。若是事务记录为空,则认为 Try 方法还没执行,便是空回滚。空回滚的状况下,应该先插入一条事务记录,确保后续的 Try 方法不会再执行。若是插入成功,则说明 Try 方法尚未执行,空回滚继续执行。若是插入失败,则认为Try 方法正再执行,等待 TC 的重试便可。若是一开始读取事务记录不为空,则说明 Try 方法已经执行完毕,再检查状态是否为初始化,若是是,则尚未执行过其余二阶段方法,正常执行 Cancel 逻辑。若是状态为已回滚,则说明这是重复调用,容许幂等,直接返回成功便可。若是状态为已提交,则一样是一个异常,一个已提交的事务,不能再次回滚。

经过这一部分的讲解,你们应该对 TCC 模型下最多见的三类异常 Case,空回滚、幂等、悬挂的成因有所了解,也从实际例子中知道了怎么解决这三类异常,在解决了这三类异常的状况下,咱们的 TCC 接口设计就是比较完备的了。后续咱们将会把这些解决方案移植到 Seata 框架中,由 Seata 框架来完成异常的处理,开发 TCC 接口的同窗就再也不须要关心了。

四、 TCC 性能优化

虽然 TCC 模型已经完备,可是随着业务的增加,对于 TCC 模型的挑战也愈来愈大,可能还须要一些特殊的优化,才能知足业务需求。下面咱们将会给你们讲讲,蚂蚁金服内部在 TCC 模型上都作了哪些优化。

4.1 同库模式

第一个优化方案是改成同库模式。同库模式简单来讲,就是分支事务记录与业务数据在相同的库中。什么意思呢?以前提到,在注册分支事务记录的时候,框架的调用方切面会先向 TC 注册一个分支事务记录,注册成功后,才会继续往下执行 RPC 调用。TC 在收到分支事务记录注册请求后,会往本身的数据库里插入一条分支事务记录,从而保证事务数据的持久化存储。那同库模式就是调用方切面再也不向 TC 注册了,而是直接往业务的数据库里插入一条事务记录。

幻灯片33.JPG

在讲解同库模式的性能优化点以前,先给你们简单讲讲同库模式的恢复逻辑。一个分布式事务的提交或回滚仍是由发起方通知 TC,可是因为分支事务记录保存在业务数据库,而不是 TC 端。所以,TC 不知道有哪些分支事务记录,在收到提交或回滚的通知后,仅仅是记录一下该分布式事务的状态。那分支事务记录怎么真正执行第二阶段呢?须要在各个参与者内部启动一个异步任务,按期捞取业务数据库中未结束的分支事务记录,而后向 TC 检查整个分布式事务的状态,即图中的 StateCheckRequest 请求。TC 在收到这个请求后,会根据以前保存的分布式事务的状态,告诉参与者是提交仍是回滚,从而完成分支事务记录。

幻灯片34.JPG

那这样作有什么好处呢?左边是采用同库模式前的调用关系图,在每次调用一个参与者的时候,都是先向 TC 注册一个分布式事务记录,TC 再持久化存储在本身的数据库中,也就是说,一个分支事务记录的注册,包含一次 RPC 和一次持久化存储。

右边是优化后的调用关系图。从图中能够看出,每次调用一个参与者的时候,都是直接保存在业务的数据库中,从而减小与 TC 之间的 RPC 调用。优化后,有多少个参与者,就节约多少次 RPC 调用。

这就是同库模式的性能方案。把分支事务记录保存在业务数据库中,从而减小与 TC 的 RPC 调用。

4.2 异步化

另一个性能优化方式就是异步化,什么是异步化。TCC 模型的一个做用就是把两阶段拆分红了两个独立的阶段,经过资源业务锁定的方式进行关联。资源业务锁定方式的好处在于,既不会阻塞其余事务在第一阶段对于相同资源的继续使用,也不会影响本事务第二阶段的正确执行。从理论上来讲,只要业务容许,事务的第二阶段何时执行均可以,反正资源已经业务锁定,不会有其余事务动用该事务锁定的资源。

假设只有一个中间帐户的状况下,每次调用支付服务的 Commit 接口,都会锁定中间帐户,中间帐户存在热点性能问题。

可是,在担保交易场景中,七天之后才须要将资金从中间帐户划拨给商户,中间帐户并不须要对外展现。所以,在执行完支付服务的第一阶段后,就能够认为本次交易的支付环节已经完成,并向用户和商户返回支付成功的结果,并不须要立刻执行支付服务二阶段的 Commit 接口,等到低锋期时,再慢慢消化,异步地执行。

五、总结

今天进行了 Seata TCC 模式的深度解析。主要介绍 Seata TCC 模式的原理,从 TCC 业务模型与并发控制的角度告诉你们怎么设计一个 TCC 的接口以及怎么处理空回滚、幂等、悬挂等异常,最后对蚂蚁金服内部对 TCC 的性能优化点简单介绍,使得 TCC 模式可以知足更高的业务需求。

业务各有不一样,有些业务能容忍短时间不一致,有些业务的操做能够幂等,不管什么样的分布式事务解决方案都有其优缺点,没有一个银弹可以适配全部。所以,业务须要什么样的解决方案,还须要结合自身的业务需求、业务特色、技术架构以及各解决方案的特性,综合分析,才能找到最适合的方案。

若是你们对 Seata 的性能和需求有本身的想法,欢迎你们在钉钉群(搜索群号便可加入:23127468)或者 Github 上与咱们讨论交流。

Seata:github.com/seata/seata

今天的直播分享到这里结束了,谢谢你们!

本期视频回顾以及 PPT 查看地址

tech.antfin.com/activities/…

往期直播精彩回顾

公众号:金融级分布式架构(Antfin_SOFA)

相关文章
相关标签/搜索