Spring Cloud Alibaba教程11:分布式事务解决方案及理论基础

前言

在传统的单体应用架构中,例如经典的SSM,项目会采用分层架构模式:数据库访问层、业务逻辑层、控制层,从前端到后台所有的代码都是一个或者几个开发者去完成,该架构模式没有对我们业务逻辑代码实现拆分、也没有对数据源进行拆分,所有的代码都写入到同一个项目的不同module中。此时多个不同业务逻辑都是在同一个数据源中实现事务管理,是不存在分布式事务的问题,因为在同一个数据源的情况下,都是采用事务管理器,相当于每个事务管理器对应一个数据源。

如果使用分布式、微服务的架构风格来开发项目、在数据源得到足够拆分的情况下,必然会产生分布式事务问题,历来分布式事务是分布式系统中的一大难题,好在是alibaba开源了Seata,可帮助开发团队快速解决分布式事务难题,正好我所开发的项目中产生了分布式事务场景,因此Seata成为了首选,那么在学习Seata之前,我们先通过一篇文章介绍分布式事务相关概念以及常见的解决方案,帮助大家对分布式事务基础概念有个大致的了解。

一、什么是分布式事务

在了解什么是分布式事务之前首先要了解以下关于事务的几个概念:

1. 事务

事务是由一组操作构成的可靠的独立的工作单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。事务具备ACID的特性,即原子性、一致性、隔离性和持久性。

  • 原子性a :要么提交 要么回滚
  • 一致性c :指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。
  • 隔离性i :多个事务在一起执行的时候,互不影响;
  • 持久性d :事务一旦提交或者回滚后,不会在对该结果有任何影响

2. 本地事务

本地事务就是用关系数据库来控制事务,关系数据库通常都具有ACID特性,传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。
在这里插入图片描述

3. 分布式事务

随着业务需求的飞速发展和项目架构发生的巨大变化,单体应用已经完全不能满足业务需求,整体架构由原来的单体应用逐渐拆分成为了微服务,原来的3个服务被从一个单体架构上拆开了,成为了3个独立的微服务,每个微服务分别使用自己的数据库,也不在之前共享同一个数据源了,具体的业务将由三个服务的调用来共同完成,如图:
在这里插入图片描述

每一个服务的内部数据一致性仍然有本地事务来保证。但是面对整个业务流程上的事务应该如何保证呢?这就是在微服务架构下面临的挑战,如何保证在微服务中的数据一致性


二、主流分布式事务解决框架

  • 1.单体项目多数据源情况 可以使用JTA+ Atomikos。
  • 2.基于RabbitMQ的形式解决 ,采用最终一致性的思想。
  • 3.基于RocketMQ解决分布式事务 ,采用可靠性事务消息机制。
  • 4.国产LCN分布式事务框架,采用lcn模式 ,原理使用假关闭连接 (公司倒闭,目前已经被淘汰: http://www.txlcn.org/zh-cn/),LCN并不生产事务,LCN只是本地事务的协调工。
  • 5.Spring Cloud Alibaba开源的Seata ,未来可能是分布式事务的主流解决方案,当下比较推荐。

三、分布式事务核心思想

关于分布式事务解决方案,核心思想有: 2pc(基于两阶段提交协议+最终一致性)、Tcc(补偿机制)

1. 2pc两阶段提交方案(XA)

两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务,协调者会通过两次阶段实现数据最终的一致性的。

  • 准备阶段
    协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
    在这里插入图片描述

  • 提交阶段
    如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚
    在这里插入图片描述

2. Tcc事务的补偿机制

TCC是Try-Confirm-Cancel的简称:

Try阶段:

完成所有业务检查(一致性),预留业务资源(准隔离性)
回顾上面航班预定案例的阶段1,机票就是业务资源,所有的资源提供者(航空公司)预留都成功,try阶段才算陈宫

Confirm阶段:

确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,美团APP确认两个航空公司机票都预留成功,因此向两个航空公司分别发送确认购买的请求。

Cancel阶段:
取消Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,如果某个业务方的业务资源没有预留成功,则取消所有业务资源预留请求。

敏锐的读者立马会想到,TCC与XA两阶段提交有着异曲同工之妙,下图列出了二者之间的对比:
在这里插入图片描述
1) 在阶段1:
在XA中,各个RM准备提交各自的事务分支,事实上就是准备提交资源的更新操作(insert、delete、update等);而在TCC中,是主业务活动请求(try)各个从业务服务预留资源。

2) 在阶段2
XA根据第一阶段每个RM是否都prepare成功,判断是要提交还是回滚。如果都prepare成功,那么就commit每个事务分支,反之则rollback每个事务分支。

TCC中,如果在第一阶段所有业务资源都预留成功,那么confirm各个从业务服务,否则取消(cancel)所有从业务服务的资源预留请求。

TCC两阶段提交与XA两阶段提交的区别是:

XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。

XA事务中的两阶段提交内部过程是对开发者屏蔽的,回顾我们之前讲解JTA规范时,通过UserTransaction的commit方法来提交全局事务,这只是一次方法调用,其内部会委派给TransactionManager进行真正的两阶段提交,因此开发者从代码层面是感知不到这个过程的。而事务管理器在两阶段提交过程中,从prepare到commit/rollback过程中,资源实际上一直都是被加锁的。如果有其他人需要更新这两条记录,那么就必须等待锁释放。

TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。

TCC中的两阶段提交并没有对开发者完全屏蔽,也就是说从代码层面,开发者是可以感受到两阶段提交的存在。如上述航班预定案例:在第一阶段,航空公司需要提供try接口(机票资源预留)。在第二阶段,航空公司提需要提供confirm/cancel接口(确认购买机票/取消预留)。开发者明显的感知到了两阶段提交过程的存在。try、confirm/cancel在执行过程中,一般都会开启各自的本地事务,来保证方法内部业务逻辑的ACID特性。其中:

1、try过程的本地事务,是保证资源预留的业务逻辑的正确性。

2、confirm/cancel执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务(Compensation-Based Transactions)。

由于是多个独立的本地事务,因此不会对资源一直加锁。

另外,这里提到confirm/cancel执行的本地事务是补偿性事务,关于什么事补偿性事务,atomikos 官网上有以下描述:
C6467FCC-0325-4DA9-9B21-D263C19710D2.png

红色框中的内容,是对补偿性事务的解释。大致含义是,"补偿是一个独立的支持ACID特性的本地事务,用于在逻辑上取消服务提供者上一个ACID事务造成的影响,对于一个长事务(long-running transaction),与其实现一个巨大的分布式ACID事务,不如使用基于补偿性的方案,把每一次服务调用当做一个较短的本地ACID事务来处理,执行完就立即提交”。

在这里,笔者理解为confirm和cancel就是补偿事务,用于取消try阶段本地事务造成的影响。因为第一阶段try只是预留资源,之后必须要明确的告诉服务提供者,这个资源你到底要不要,对应第二阶段的confirm/cancel。

提示:读者现在应该明白为什么把TCC叫做两阶段补偿性事务了,提交过程分为2个阶段,第二阶段的confirm/cancel执行的事务属于补偿事务