08、分布式事务 实战 - TCC 分布式事务原理

一、TCC 核心思想

TCC分布式事务最核心的思想就是在应用层将一个完整的事务操作分为三个阶段。在某种程度上讲,TCC 是一种资源,实现了 Try、Confirm、Cancel 三个操作接口。与传统的两阶段提交协议不同的是,TCC 是一种在应用层实现的两阶段提交协议,在 TCC 分布式事务中,对每个业务操作都会分为 Try、Confirm 和 Cancel 三个阶段,每个阶段所关注的重点不同,如下图所示:

 

Try 阶段

Try阶段是准备执行业务的阶段,在这个阶段尝试执行业务,重点关注如下事项:

1、 完成所有的业务检查,确保数据的一致性;
2、 预留必要的业务资源,确保数据的隔离性;

在下单扣减库存的业务场景中,如果使用了 TCC 分布式事务,则需要在 Try 阶段检查商品的库存数量是否大于或者等于下单提交的商品数量,如果商品的库存数量大于或者等于下单提交的商品数量,则标记扣减库存数量。此时的商品数量并没有真正扣减,只是做资源预留操作,并且会将订单信息保存到数据库,标记为待提交状态。如果商品的库存数量小于下单提交的商品数量,则提示用户库存不足,并且删除提交的订单数据或者将订单状态标记为删除

Confirm 阶段

Confirm 阶段是确认执行业务的阶段,在这个阶段确认执行的业务。此时,重点关注如下事项:

1、 真正执行业务;
2、 不做任何业务逻辑检查,直接将数据持久化到数据库;
3、 直接使用Try阶段预留的业务资源;

在下单扣减库存的业务场景中,由于在 Try 阶段已经检查过商品的库存数量大于或者等于下单提交的商品数量,因此在 Confirm 阶段不会进行二次检查,直接将订单的抓过你太更新为 “已提交”,并且真正执行扣减库存操作。在 Confrim 阶段是真正地执行业务操作,其间不会做任何业务检查,直接使用 Try 阶段预留的业务资源

Cancel 阶段

Cancel 阶段取消执行业务,重点关注如下事项:

1、 释放Try阶段预留的业务资源;
2、 将数据库中的数据恢复到最初的状态;

在下单扣减库存地业务场景中,假设 Try 阶段检查的结果为商品地库存数量大于或者等于下单提交的商品数量,在执行完订单提交业务后,执行扣减库存操作时发生异常,或者在执行 Confirm 阶段地业务时发生异常。此时,会执行 Cancel 阶段的操作回滚业务,使数据回到提交订单之前的状态

在某种程度上,TCC 分布式事务的三个阶段与关系型数据库的事务操作也存在类似的地方,如下图所示:

 

在一个分布式或微服务系统中,TCC 分布式事务的 Try 阶段是先把多个应用中的业务资源锁定,预留执行分布式事务的资源。同样,关系型数据库的 DML 操作会锁定数据库的行记录,持有数据库的资源。TCC 分布式事务的 Confirm 操作是在所有涉及分布式事务的应用的 Try 阶段都执行成功后确认并提交最终事务状态的操作,而关系型数据库的 Commit 操作是在所有的 DML 操作执行成功之后提交事务。TCC 分布式事务的 Cancel 操作是在涉及分布式事务的应用没有全部执行成功时,将已经执行成功的应用进行回滚,而关系型数据库的回滚操作是在执行的 DML 操作存在异常时执行的。关系型数据库中的 Commit 操作和 Rollback 操作也是一对反向业务操作,TCC 分布式事务中的 Confirm 操作和 Cancel 操作也是一对反向业务操作

另外,由于使用 TCC 分布式事务时,各业务系统的事务未达到最终状态时,会存在短暂的数据不一致现象,因此各业务系统需要具备兼容数据最终一致性之前带来的可见性问题的能力

二、TCC 实现原理

TCC分布式事务在应用层将整体事务的执行分为 Try、Confirm、Cancel 三个阶段。每个阶段的执行不会过多地占用数据库资源,而是在 Try 阶段预留事务必须的业务资源。TCC 分布式事务的实现与其核心原理密不可分

1.TCC 核心组成

一个完整的 TCC 分布式事务需要包含三个部分:主业务服务、从业务服务和 TCC 管理器,如下图所示:

 

主业务服务是 TCC 分布式事务的发起方,在下单扣减库存的业务场景中,订单服务是 TCC 分布式事务的发起方,就是主业务服务

从业务服务主要负责提供 TCC 业务操作,是整个业务活动的操作方。从业务活动必须实现 TCC 分布式事务 Try、Confirm 和 Cancel 三个阶段的接口,供主业务服务调用。由于在 TCC 分布式事务的执行过程中,Confirm 阶段的操作和 Cancel 阶段的操作可能会被执行多次,因此需要 Confirm 阶段的操作和 Cancel 阶段的操作保证幂等性

TCC管理器在整个 TCC 分布式事务的执行过程中,管理并控制着整个事务活动,包括记录并维护 TCC 全局事务的事务状态和每个从业务服务的分支事务状态,并在参与分布式事务的所有分支事务的 Try 阶段都执行成功时,自动调用每个分支事务的 Confirm 阶段的操作,完成分布式事务,同时会在参与分布式事务的某些分支事务执行失败时,自动调用分支事务的 Cancel 操作回滚分布式事务

2.TCC 核心原理

在使用TCC 分布式事务解决分布式场景下的数据一致性问题时,需要将原本的一个事务接口改造成三个不同的事务逻辑,也就是前文说的 Try 阶段、Confirm 阶段和 Cancel 阶段

原本一个接口的方法完成的事务逻辑也要分拆成如下执行流程:

1、 依次执行所有参与TCC分布式事务的分支事务Try阶段的操作;
2、 如果每个分支事务Try阶段的逻辑都执行成功,则TCC分布式事务管理器会自动调用每个分支事务Confirm阶段的方法并执行,完成整个分布式事务的逻辑;
3、 如果某个分支事务的Try逻辑或者Confirm逻辑的执行出现问题,则TCC分布式事务管理器会自动感知这些异常信息,然后自动调用每个分支事务Cancel阶段的方法执行Cancel逻辑,回滚之前执行的各种操作,使数据恢复到执行TCC分布式事务之前的状态;

讲得直白点,就是如果遇到如下情况,TCC 分布式事务会在 Try 阶段检查参与分布式事务的各个服务、数据库和资源是否都能够保证分布式事务正常执行,能否将执行分布式事务的资源预留出来,而不是先执行业务逻辑操作:

1、 数据库或其他数据存储服务宕机;
2、 某个应用服务宕机;
3、 参与分布式事务的资源不足;

如果参与分布式事务的服务都正常执行了,也就是说,数据库或其他数据存储能够正常提供服务,所有参与分布式事务的应用服务正常,执行分布式事务时需要的资源充足,并且在 Try 阶段顺利预留出执行分布式事务需要的资源,再执行 TCC 分布式事务的 Confirm 阶段,就能够大概率保证分布式事务成功执行

如果在Try 阶段,某个服务执行失败了,可能是数据库或者其他数据存储宕机了,或者是这个服务宕机了,也有可能是这个服务对应的数据资源不足。此时,会自动执行各个服务 Cancel 阶段的的逻辑,回滚 Try 阶段执行的业务逻辑,将数据恢复到执行分布式事务之前的状态

其实,通过上面的逻辑,TCC 分布式事务还是不能保证执行结果数据的一致性。这里存在一个问题,那就是如果发生了异常情况,例如,在下单扣减库存的业务场景中,订单服务突然宕机,然后重启订单服务,TCC 分布式事务如何保证之前没有执行完的事务继续执行呢?

这种问题在实际的业务场景中是经常出现的,在设计 TCC 分布式事务框架时必须要考虑这种异常场景。在执行 TCC 分布式事务时,需要记录一些分布式事务的活动日志,将这些活动日志存储到文件或者数据库中,将分布式事务的各个阶段和每个阶段执行的状态全部记录下来

除了参与 TCC 分布式事务的某些服务宕机这种问题,还需要注意空回滚、幂等和悬挂等问题

综,可以得出 TCC 分布式事务总体执行的示意图:

 

无论是主业务服务还是从业务服务,在执行 TCC 分布式事务时,都需要 TCC 事务管理器的参与。在实际业务场景中,TCC 事务管理器作为某一种具体的 TCC 分布式事务框架,例如 Dromara 开源社区的 Hmily 框架,会在 Try 阶段进行业务检查、预留业务资源。在 Confirm 阶段不再进行业务检查,使用 Try 阶段预留的业务资源真正地执行业务操作。在 Cancel 阶段释放 Try 阶段预留的资源,使数据回滚到执行 YCC 分布式事务之前地状态。在 TCC 分布式事务地每个姐u但,TCC 事务管理器都会将各个阶段和每个阶段执行的状态全部记录到事务记录数据库或者事务记录文件中

通过上面的执行逻辑,只要业务逻辑中不存在明显的 Bug 和异常,TCC 分布式事务就能够保证所有参与分布式事务的服务逻辑要么全部执行成功,要么全部不执行

三、TCC 核心流程

为了更好地理解 TCC 分布式事务的执行流程程,本节以电商业务场景中提交订单、扣减库存、增加积分、创建出库单的场景为例,简单介绍 TCC 分布式事务每个阶段的核心执行流程

1.业务场景介绍

在电商业务场景中,一个典型的业务场景就是支付订单。这个场景包含修改订单抓过你太、扣减库存、增加积分、创建出库单等业务,这些业务要么全部执行成功, 要么全部执行失败,必须是一个完整的事务。如果不能构成一个完整的事务,就有可能出现库存未扣减或者超卖的问题

如果没有使用分布式事务,在订单服务中,修改订单状态成功,调用远程的库存服务出现异常,此时有可能出现修改订单成功,但库存未扣减的情况,如下图所示:

 

如果库存服务出现异常,则订单服务在第 ② 步调用库存服务执行扣减库存的操作就失败了,随后调用积分服务增加积分和调用存储服务生成出库单却都成功了,此时就出现了支付修改订单状态成功,商品库存未扣减的情况

如果修改订单状态的操作执行失败,而调用库存服务扣减商品库存的操作执行成功,此时就出现了商品库存被异常扣减的情况,可能会导致超卖的现象,如下图所示:

 

在支付订单的场景中,第 ① 步更新订单操作失败,但是后续调用库存服务、积分服务和仓储服务都执行成功,这种情况可能就是用户支付失败,或者取消了支付,但是仍旧扣减了库存、增加了用户积分、提交了出库单,最终可能会导致商品超卖的严重问题

在电商支付订单的业务场景中,涉及多个服务之间的调用,为了保证数据的一致性,必须使用分布式事务。接下来,结合电商支付订单的业务场景,简单介绍一下 TCC 分布式事务中每个阶段的执行流程

2.Try 阶段流程

在电商支付订单的业务场景中,为了保证最终数据的一致性,对于订单服务,不能将订单状态直接更新为 “支付成功”,而是要先更新为 “支付中” 的状态;对于库存服务,也不能直接扣减库存,而是要扣减库存后在冻结库存的字段中保存扣减库存的数量;对于积分服务,不能直接为用户账户增加积分,而是要在单独的字段中设置用户应该增加的积分;对于仓储服务,创建的出库单状态应该被标记为 “不确定”,如下图所示:

 

在电商支付订单的场景中,Try 阶段主要的业务流程如下所示:

1、 订单服务将订单数据库中订单的状态更新为“支付中”;
2、 订单服务调用库存服务冻结部分库存,将冻结的库存数量也就是用户下单时提交的商品数量,单独写入商品库存表的冻结字段中,同时将商品库存数量减去冻结的商品数量;
3、 订单服务调用积分服务进行预增加积分的操作,在用户积分数据表中,将要增加的积分写入单独的预增加积分字段中,而不是直接增加用户的积分;
4、 订单服务调用仓储服务生成出库单时,将出库单的状态标记为“未知”,并不直接生成正常的出库单;

3.Confirm 阶段流程

如果Try 阶段的业务逻辑全部执行成功,则 TCC 分布式事务会执行 Confirm 阶段的业务逻辑。在实际场景中,Confirm 阶段的执行往往是由 TCC 分布式事务框架调用完成的。在订单服务中会将订单的状态由 “支付中” 更新为 “支付成功”。在库存服务中会真正地扣减库存,将写入冻结字段的库存数量减去当次下单时提交的商品数量。在积分服务中会将当次支付产生的积分从预增加积分字段中扣除,并将对应的积分增加到用户积分账户中。在仓储服务中会将出库单的状态由 “未知” 更新为 “已创建”,如下图所示:

 

在电商支付订单的场景中,Confirm 阶段主要的业务流程如下所示:

1、 订单服务将订单数据库中订单的状态更新为“支付成功”;
2、 TCC分布式事务框架调用库存服务中Confirm阶段的方法,真正地扣减库存,将预扣减字段中的库存数量减去当次下单提交的商品数量;
3、 TCC分布式事务框架调用积分服务中Confirm阶段的方法,真正地增加积分,将预增加积分字段中的积分数量减去当次支付产生地积分数量,并且在用户的积分账户中增加当次支付产生的积分数量;
4、 TCC分布式事务框架调用仓储服务中Confirm阶段的方法,将出库单的状态更新为“已创建”;

4.Cancel 阶段流程

如果Try 阶段的业务执行失败,或者某个服务出现异常等,TCC 分布式事务框架能够感知到这些异常信息,会自动执行 Cancel 阶段的流程,对整个 TCC 分布式事务进行回滚。在实际场景中,Cancel 阶段的执行往往是由 TCC 分布式事务框架调用完成的

在订单服务中,将订单的状态更新为 “已取消”。在库存服务中,将当次下单提交的商品数量加回到商品库存字段中,并且在预扣减库存的字段中减去当次下单提交的商品数量。在积分服务中,在预增加积分字段中减去当次支付产生的积分数量。在仓储服务中,将出库单的状态标记为 “已取消”。具体流程如下所示:

 

在电商支付的场景中,Cancel 阶段主要的业务流程如下所示:

1、 订单服务将订单数据库中订单的状态标记为“已取消”;
2、 TCC分布式事务框架调用库存服务Cancel阶段的方法进行事务回滚,将库存数据表中的预扣减库存字段中存储的商品数量减去当次下单提交的商品数量,并且将库存数据表中的商品库存字段存储的商品库存数量增加当次下单提交的商品数量;
3、 TCC分布式事务框架调用积分服务Cancel阶段的方法进行事务回滚,将积分数据中的预增加积分字段中的积分数量减去当次支付产生的积分数量;
4、 TCC分布式事务框架调用仓储服务Cancel阶段的方法进行事务回滚,将出库单的状态标记为“已取消”;

四、TCC 关键技术

在TCC 事务管理器,也就是 TCC 分布式事务框架的实现过程中,有几项关键技术需要注意。本节就以 Dromara 开源社区的 TCC 分布式事务框架 Hmily 为例,简单介绍实现 TCC 分布式事务框架的关键技术

AOP 切面

实现TC 分布式事务的第一个核心技术就是 AOP 切面。无论是 TCC 分布式事务的发起者,还是参与者,都需要经过 AOP 切面的处理。通过 AOP 切面拦截具体的业务逻辑,在 AOP 切面中执行事务日志的记录、远程调用等逻辑。Hmily 框架中大量使用了 Spring 的 AOP 切面,处理分布式事务问题

反射技术

实现TCC 分布式事务的第二个核心技术就是反射技术。TCC 分布式事务中 Confirm 阶段的方法和 Cancel 阶段的方法是通过反射技术调用的。这也就是 Hmily 框架在 Try 阶段的方法上使用注解来指定 Confirm 方法和 Cancel 方法的原因。在 Try 阶段的方法上使用注解指定 Confirm 方法和 Cancel 方法,Hmily 框架会在执行完 Try 方法后,使用反射技术自动调用 Confirm 方法或者 Cancel 方法

持久化技术

实现TCC 分布式事务的第三个核心技术就是持久化技术。在分布式事务的实现中,所有参与事务的服务都存在数据的持久化操作。在分布式环境中,由于网络的不稳定性,随时都有可能出现调用服务方法失败的情况,在 TCC 分布式事务中,需要保证数据的最终一致性。如果只有一部分服务的请求被正常处理,则另一部分的请求最终也需要被处理,对请求数据持久化是必不可少的。Hmily 框架不仅支持使用 Redis、ZooKeeper、文件、缓存、ETCD、MongoDB 等进行持久化操作,还提供了 SPI 扩展接口,使具体业务能够根据实际需求实现自身的持久化技术

序列化技术

实现TCC 分布式事务的第四个核心技术就是序列化技术。在分布式环境中,数据的持久化和在网络传输中的传输,都需要序列化技术的支持。Hmily 框架支持的序列化技术包括 JDK 自带的序列化技术、Hessiian 序列化技术、Kyro 序列化技术、MsgPack 序列化技术和 ProtoBuf 序列化技术。另外,Hmily 框架还提供了 SPI 扩展接口,使具体的业务能够根据实际需求实现自身的序列化技术

定时任务

实现TCC 分布式事务的第五个核心技术就是定时任务。在分布式环境中,由于网络的不稳定性,难免会出现方法调用失败的情况,此时,需要利用定时任务来重试方法的调用操作。Hmily 框架实现了当方法调用失败时,使用定时任务进行重试的机制

动态代理

实现TCC 分布式事务的第六个核心技术就是动态代理。分布式环境中存在很多远程调用框架,在分布式事务的实现过程中,需要通过动态代理的方式支持多种远程调用框架,在分布式事务的实现过程中,需要通过动态代理的方式支持多种远程调用框架。例如,在 Hmily 框架中通过动态代理支持多种远程调用框架,这些远程调用框架包括 Apache Dubbo、Alibaba Dubbo、BRPC、gRPC、Motan、Sofa-RPC、Spring Cloud、Tars 等

多配置源技术

实现TCC 分布式事务的第七个核心技术就是多配置源技术,在分布式环境中,为了便于管理各业务系统的配置,往往会几种存储各业务系统的配置,并通过相应的技术快速同步到各业务系统的本地缓存中。由于在真正的业务场景中,会存在不同的配置存储技术,因此实现分布式事务时,需要支持多配置源技术。例如,在 Hmily 框架中,就实现了多种配置源技术,这些配置源包括 Apollo、Consul、ETCD、Loader、Nacos、Zookeeper、本地存储等。另外,Hmily 框架还提供了 SPI 扩展接口,使具体的业务能够根据实际需求实现自身的配置源技术