前言

Seata框架的介绍

Seata中的模式介绍

XA模式

第一阶段

第二阶段

XA模式的优缺点

AT模式(推荐)

第一阶段

第二阶段

AT模式的脏写问题

脏写问题解决思路

AT模式的优缺点

TCC模式

TCC模式流程分析

TCC模式的两个阶段

TCC模式的优缺点


前言

在微服务中我们无法保证数据库的强一致性,因为CAP定理中说明可用性和一致性只能选择其中一种,基于CAP定理就有了BASE理论,BASE理论中提到了虽然无法保证数据库的强一致性,但是可以保持最终一致,也就是说中间会有一个软状态。

我们要实现保证多个微服务对数据库的操作一致性,可以使用阿里云开发的Seata框架,Seata框架是基于BASE理论来实现的。

以下我给大家介绍几种方式,大家可以根据业务需求来使用

Seata框架的介绍

在这套框架中有以下三个重要角色:

TC (Transaction Coordinator)事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。

TM (Transaction Manager) 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。

RM (Resource Manager) 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

Seata基于上述架构提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入 CP
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式 AP
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • SAGA模式:长事务模式,有业务侵入

整体框架如下:

Seata中的模式介绍

XA模式

我们先看一下XA模式的流程图:

XA模式的工作流程分为两个阶段

第一阶段

首先由TM(事务管理器)来开启一个全局事务,全局事务开启后调用分支事务,在执行业务sql之前,资源管理器会先到事务协调者注册一个分支事务,注册完成后再执行sql语句;资源管理器在sql执行完成后会给事务协调者报告一个事务状态。

在这里需要注意的是分支事务在执行完sql之后并没有直接进行commit,而是在等待其他分支事务执行完成。

第二阶段

无异常的情况:在事务管理器执行完业务代码后会告诉TC(事务协调者)进行提交,在事务协调者检查各个事务执行状态确实没有问题的时候,会通知各个分支事务来进行提交。

出异常的情况:事务管理器执行业务的时候出现了异常,这个时侯的事务管理器会通知事务协调者进行回滚,当事务协调者接收到来着事务管理器的回滚信息时,就通知已经执行完sql的事务来进行rollback回滚操作

XA模式的优缺点

优点

1.事务的强一致性,满足ACID原则(关系型数据库原生就支持ACID);

2.常用的数据库都支持,实用简单,并且没有代码侵入

缺点

1.因为第一阶段需要锁定数据库资源,等待第二阶段结束才能释放,性能较差

2.依赖关系型数据库实现事务

AT模式(推荐)

我们先来看一下AT模式的简单流程图:

在AT模式下同样是分为了两个阶段

第一阶段

首先由TM(事务管理器)来开启一个全局事务,全局事务开启后调用分支事务,在执行业务sql之前,资源管理器会先到事务协调者注册一个分支事务,注册完成后再执行sql语句;与XA模式不同的是在执行sql,Seata会记录sql更新前后的快照并存储到undo_log(在业务的数据库中定义)表中。

在sql语句执行完成并且没有异常后会直接commit,不会等待其他分支事务完成再提交,所以AT模式比XA模式性能要好一些,分支事务执行完成后向事务协调者报告事务的状态。

第二阶段

无异常的情况:事务管理器执行完之后会通知事务协调者提交,事务协调者接收到通知后会检查所有分支的事务状态,确定没问题后通知分支事务进行commit,但是AT模式下,sql在执行完后就已经进行了commit,所以事务协调者通知分支事务提交后,分支事务会到undo_log表中把当前分支事务记录的快照数据进行删除。

有异常的情况:事务管理器执行时发生了异常通知事务协调者进行回滚,事务协调者接收到回滚的通知后再通知分支事务进行回滚,但是我们的sql语句在执行完之后就已经commit了,所有无法rollback回滚,但是在undo_log表中有分支事务在执行前后的记录,所有在回滚操作中会进行一个反向操作来恢复更新前的数据。

AT模式的脏写问题

在多线程的场景下可能出现脏写问题,如图:

事务1是全局事务(@GlobalTransactional),事务二是本地事务(@Transactional)

在并发的场景下,事务1开启全局事务对money字段进行修改为90并记录sql执行前后的数据保存到undo_log表,在分支事务执行完后就直接获取DB锁进行commit提交

但是在事务1还没有执行完,这时来了一个修改金额的请求,事务2开始获取DB锁并执行修改money的sql语句,然后直接提交事务,这个时候的数据库中的money字段变成了80。

这个时候事务1中的其他分支事务出现异常要进行回滚,开始获取DB锁,根据undo_log中记录的快照来恢复数据,这样就出现了脏写的问题,事务1的回滚会直接把数据库中的字段回滚成100。

脏写问题解决思路

解决脏写的流程图:

解决思路就是引入全局锁的概念,但是需要注意的是全局锁是由seata来维护的一张表,如果项目中还是同时存在受Seata管理的事务和不受Seata管理的事务,还是会存在脏写问题,因此在记录快照的时候要同时记录before image和after image。

加上全局锁后的执行流程:事务1和事务2都是全局事务,事务在执行数据库操作的时候会先获取全局锁,执行完成后会进行commit提交并释放DB锁,在事务1释放了DB锁后,事务1继续执行的时候发生异常想要获取DB锁进行回滚,但是事务2已经在执行业务sql并获取到了DB锁在等待全局锁并提交。

这个时候就出现了一个问题,事务1在等待获取DB锁进行恢复数据,事务2已经获取了DB锁,在等待事务1释放全局锁,出现了互相等待的问题,但是在Seata的机制中,事务获取全局锁失败的话会每间隔10毫秒获取一次,默认获取30次,一共是等待300毫秒,获取DB等待的时间要比300毫秒长,因此事务2在300毫秒后获取不到全局锁后就会进行事务回滚并释放DB锁。

AT模式的优缺点

AT模式的优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能比较好
  • 利用全局锁实现事务的隔离
  • 没有代码侵入,框架自动完成回滚和提交

AT模式的缺点:

  • 两阶段之间属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但比XA模式要好很多
  • 如果同时存在不受seata管理的本地事务,则可能会导致seata回滚失败

TCC模式

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:

    • Try:资源的检测和预留;
    • Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
    • Cancel:预留资源释放,可以理解为try的反向操作。

TCC模式流程分析

流程图:

其实TCC模式和AT模式类似,所以这里就来说一下其中的一些不一样的地方

TCC模式的两个阶段

在这里就用余额来做一个举例,假设有100元的余额

第一阶段(Try):先检查余额是否充足,如果充足则冻结金额金额增加30元,可用余额扣除30元,进行一个资源预留,此时总金额 = 冻结金额 + 可用金额,数量依然是100不变。事务直接提交无需等待其它事务。

第二阶段(Confirm)无异常情况:假如要提交,则冻结金额扣减30元确认可以提交,不过之前可用金额已经扣减过了,所以只要清除冻结金额就好了,此时,总金额 = 冻结金额 + 可用金额 = 0 + 70 = 70元

第二阶段(Cancel)有异常情况:进行回滚操作,冻结金额扣减30,可用余额增加30,那么就要释放冻结金额,恢复可用金额。

TCC模式的优缺点

优点

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

缺点

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理