Seata分布式事务解决方案
1.基础概念
1.1 本地事务
在各类应用系统中,最常见的事务是利用关系型数据库本身的事务特性来实现的,这种类型的事务叫数据库事务。因为这种类型事务中所有操作都在一个应用的同一个数据库连接中完成,所以又被称为本地事务。
使用数据库本身的事务机制所实现的本地事务:
begin transaction;
//1.本地数据库操作:张三减少金额
//2.本地数据库操作:李四增加金额
commit transaction;
1.2 分布式事务
随着互联网的快速发展,软件系统由原来的单体应用转变为分布式应用。
一个分布式系统会把各个应用模块拆分为多个可独立部署的服务,因此有些事务中的操作会涉及服务与服务之间远程协作。这时,使用传统数据库事务不能保证ACID:
begin transaction;
//1.本地数据库操作:张三减少金额
//2.远程调用:让李四增加金额
commit transaction;
可以设想,当远程调用让李四增加金额成功了,由于网络问题远程调用并没有返回,此时本地事务认为提交失败就会回滚张三减少金额的操作,此时就会产生数据不一致。
在分布式架构下的事务控制,需要使用分布式事务。分布式事务的定义是:分布式系统环境下由不同的服务之间通过网络远程协作完成事务。
从另一个角度,可以由另一组名词来描述本地事务和分布式事务;一个分布式事务可能会涉及多个子系统,每个子系统内的事务可以称为分支事务;相对地,整个分布式事务称为全局事务。
全局事务=分布式事务;
分支事务=本地事务;
begin transaction; --全局事务
//1.本地数据库操作:张三减少金额 --分支事务1
//2.远程调用:让李四增加金额。-- 分支事务2
commit transaction;
1.3 产生分布式事务的场景
1、 微服务之间通过远程调用完成事务操作。
即: 跨JVM进程产生分布式事务。
比如:订单微服务和库存微服务,下单的同时订单微服务请求库存微服务减库存。
2、单体系统访问多个数据库实例
当单体系统需要访问多个数据库(实例)时也会产生分布式事务。 由于数据分布在不同的数据库实例,需要通过不同的数据库连接去操作数据,此时产生分布式事务。 即:跨数据库实例产生分布式事务。
3、多服务访问同一个数据库实例
比如:订单微服务和库存微服务即使访问同一个数据库也会产生分布式事务,原因就是跨JVM进程,两个微服务使用了不同的数据库连接进行数据库操作,此时产生分布式事务。
2. 分布式事务基础理论
在了解Seata之前需要先了解一些基础理论,从而帮助我们理解Seata的分布式事务解决方案。
2.1 BASE理论
1、强一致性和最终一致性
- 强一致性要求在一个分布式数据库中任何时间查询每个结点数据都必须一致;
- 最终一致性是允许可以在一段时间内存在一些结点的数据不一致,但是经过一段时间每个结点的数据必须一致,它强调的是最终数据的一致性;
2、Base理论介绍
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。
- 基本可用: 分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如,电商网站交易付款出现问题了,商品依然可以正常浏览。
- 软状态: 不要求强一致性,允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单的"支付中"、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。
- 最终一致: 最终一致是指经过一段时间后,所有节点数据都将会达到一致。
BASE理论是通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。
满足BASE理论的事务,可以称之为“柔性事务”。Seata中在一些模式下所提供的就是柔性事务;
2.2 分布式事务的2PC协议
2PC,是Two-PhaseCommit的缩写,即两阶段提交协议。当前,商业化的分布式事务解决方案中都是基于2PC来实现的。
2PC协议将整个分布式事务流程分为两个阶段,分别是准备阶段(Prepare phase)、提交阶段(commit phase)。
在2PC的事务模型中,有事务协调者和事务参与者两种角色。
- 事务协调者负责决策整个分布式事务的提交和回滚;
- 事务参与者负责一个分支事务(本地事务)的提交和回滚。
二阶段提交的事务执行流程如下:
- 第一阶段-准备阶段(Prepare phase):
- 事务协调者通知每个参与者执行本地事务;
- 每个参与者在本地执行事务,并在本地记录Undo(用于回滚) 和Redo (用于提交) 日志。
- 各参与者向协调者反馈事务的执行结果
-
第二阶段-提交阶段(commit phase):
事务协调者基于一阶段各个参与者的反馈来判断下一步的操作。
- 若一阶段所有参与者都成功,通知所有参与者提交各自的本地事务;
- 若任一参与者失败,则通知所有参与者回滚各自的本地事务;
在参与者执行二阶段的提交或者回滚操作后,会释放所占用的资源,执行成功后给协调者返回ACK确认。
- 大部分关系数据库,如Oracle、MySQL都支持2PC协议,并提供了对应的接口。
3 Seata分布式事务解决方案
3.1 Seata简介
Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。
Seata是基于2PC实现的方案。
Seata事务管理中的角色:
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。【统揽全局,做全局决策】
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始和结束全局事务。【控制具体的事务流程】
- RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。【对接数据库实例】
begin transaction; // TM
1.本地数据库操作:张三减少金额 // RM1
2.远程调用:让李四增加金额 // RM2
commit transaction; // TM
Seata中事务的执行流程:
- TM向TC申请开启一个全局事务,全局事务创建成功会生成全局唯一的XID;XID在微服务调用链路中传播;
- RM向TC注册分支事务,纳入XID对应全局事务的范围;(对应2PC的第一阶段)
- RM执行分支事务的操作,反馈结果。(对应2PC的第一阶段)
- TM向TC发起针对此XID的全局事务的提交或回滚决议;
- TC根据实际情况进行决策,调度XID下管辖的全部分支事务完成提交或回滚请求。(对应2PC的第二阶段)
上面是对Seata中整体流程的一个概要描述。具体的,RM在执行分支事务、提交或回滚时做哪些操作取决于具体使用的模式。
Seata提供了四种不同的模式:
- XA模式
- AT模式(默认模式)
- TCC模式
- SAGA模式
3.2 XA模式
为了统一2PC接口标准,国际开放标准组织Open Group定义了2PC中TM和RM之间通讯的接口规范叫XA,几乎所有主流的数据库都对XA供了支持。
Seata的XA模式就是基于数据库的提供的XA协议接口来实现的。
XA模式的事务执行流程
在事务开始的位置,TM开启全局事务;
-
一阶段,每个分支事务RM:
注册分支事务到TC
执行分支业务sql但不提交,继续持有数据库的锁;
报告执行状态到TC
执行到事务结束位置,TM发出提交事务的申请;
-
二阶段,
TC检测各分支事务执行状态:
如果都成功,通知所有RM提交事务
如果有失败,通知所有RM回滚事务
RM接收TC指令,提交或回滚各个分支事务,释放之前持有的数据库锁;
XA模式延长了本地事务,等待TC的最终决策再进行每个本地事务的提交/回滚。通过数据库中提供的锁来保证事务的原子性和隔离性;
XA模式的优缺点:
-
优点
1)事务具有强一致性,满足ACID原则。
2)常用数据库都支持此模式,实现简单,并且没有代码侵入
XA和AT模式下,业务代码中只需进行简单配置,并在需要开启全局事务的方法上加一个注解即可,对业务的侵入很小。
-
缺点
1)因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
2)依赖关系型数据库实现事务
3.3 AT模式
AT模式下Seata工作在应用层,通过对本地关系数据库的分支事务的协调来完成全局事务。弥补了XA模型中资源锁定周期过长的缺陷。
AT模式事务执行流程
在事务开始的位置,TM开启全局事务;
-
阶段一 RM的工作:
向TC注册分支事务;
执行业务sql并提交,释放数据库的锁;
记录undo-log(执行前后的数据镜像+业务SQL)
向TC报告事务状态
执行到事务结束位置,TM发出提交事务的申请;
-
二阶段,
TC检测各分支事务执行状态:
若都成功,进行提交,RM的工作:删除undo-log即可
若有失败,进行回滚,RM的工作:根据undo-log恢复数据到更新前的状态*
(AT模式中的快照生成、回滚等动作都是由框架自动完成,没有任何代码侵入。)
AT模式的脏写问题与全局锁
由于AT模式在第一阶段就释放了数据库的锁,而此时事务并没有结束。若第二阶段进行回滚,会产生丢失修改问题:
为了解决这个问题,AT模式引入了Seata全局锁。
-
全局锁本身就是一条记录:xid-事务;table-表名,pk-数据的行;
-
全局锁由事务协调者TC控制,持有该全局锁的事务,才具备本地事务的执行权。
在一阶段提交本地事务之前,先获取全局锁。保证从第一阶段本地事务提交到整个分布式事务提交过程中的事务隔离性。
相比于XA模式,Seata的全局锁只隔离由Seata发起的操作,影响范围比数据库锁要小,所以能够提升性能;但是它存在一定的无法保证隔离的可能性; -
Seata事务和非Seata业务产生并发问题
如果其他非seata业务来修改数据,这个时候全局锁对其不起隔离性作用。
不过发生这种问题的可能性比较低,原因有三个 1)事务回滚发生率低;2)并发操作概率低;3)业务上进行保障。
AT模式的详细流程
第一阶段:准备阶段
1)解析SQL语义,找到“业务SQL" 要更新的部分业务数据,在业务数据被更新前,将其保存成"before image” (前置镜像)
2)执行“业务SQL" 更新业务数据,在业务数据更新之后, 其保存成"after image”,(后置镜像)
3)插入UNDO_LOG :前后镜像数据+业务SQL;
4)获取Seata全局锁,成功后,提交本地事务。
第二阶段: 提交情形
1)将一阶段保存的快照数据删掉即可。
2)释放Seata全局锁;
第二阶段:回滚情形
需要回滚一阶段已经执行 “业务SQL",还原业务数据。
首先要通过对比“数据库当前业务数据”和" after image "来校验是否存在脏写。
-
若两份数据完全一致就说明没有脏写,可以利用"before image"还原业务数据;
前置快照的值+业务SQL的逆SQL—>生成补偿用的SQL; -
如果不一致就说明有脏写, 出现脏写就需要转人工处理,会发送通知。
释放Seata全局锁;
AT模式的优缺点
-
优点:
1)一阶段完成直接提交事务,释放数据库资源,性能比较好
2)利用全局锁实现读写隔离
3)没有代码侵入,框架自动完成回滚和提交
-
缺点:
1)两阶段之间存在软状态,属于最终一致;
2)存在事务之间无法隔离的风险;
AT模式与XA的差别:
XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
XA模式强一致;AT模式最终一致;
3.4 TCC模式
TCC模式也是在业务层面实现的二阶段提交方案,但与AT模式不同,它是通过人工编码来实现二阶段的操作。
TCC模式两个提交阶段定义了一组接口,供每个分支事务来实现。共涉及三个方法:
- Try:进行所涉及资源的检测和预留; 预留是为了实现事务的隔离和回滚补偿的便捷实现。
- Confirm:完成事务的业务操作;要求 Try 成功 Confirm 一定要能成功,因此会进行一定次数的重试。
- Cancel:预留资源释放,可以理解为try的反向操作。
每个方法都是一个独立的数据库事务,执行后直接进行提交;
TCC模式事务执行流程
-
在第一阶段,RM:
向TC注册分支事务;
执行try方法并提交该本地事务;
向TC报告事务状态;
-
在第二阶段,RM:
若TC作出提交决议,则各个RM执行Confirm方法;
若TC作出回滚决议,则各个RM执行Cancel方法;
TCC三个接口的设计
TCC模式中并没有给出每个步骤或者方法中具体应该进行哪些事情。
实际上,TCC模式并不能像XA和AT模式一样直接将事务操作直接全部放在第一阶段,使用TCC模式需要进行一定的设计。设计一套 TCC 接口最重要的有两点:
- 第一点,需要将事务操作分解成try和confirm两部分完成。
- 第二点,在分解时要根据业务考虑如何应对事务的并发(对应事务的隔离性)。
另外,还需要兼容空回滚和防止业务悬挂问题。
- 当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。这种情况是在未执行try操作时先执行了cancel操作,这时cancel不能做实际的回滚操作,应该做空回滚。
- 对于已经空回滚的业务,如果以后继续执行try,就永远不可能cancel来进行资源释放,这就是业务悬挂。应当阻止执行空回滚后的try操作,避免悬挂;
示例
某个服务中有一个扣减用户余额的业务。假设账户A原来余额是100,需要余额扣减30元。
为了实现资源隔离,兼容空回滚、防止业务悬挂,以及幂等性要求。可以建立一张表记录当前事务id和执行状态:
TCC模式下事务的隔离性
TCC 模型的隔离性思想是:通过业务的改造,在第一阶段结束之后,从底层数据库资源层面的加锁过渡为上层业务层面的加锁,从而释放底层数据库锁资源。
继续上面的例子,账户 A 上有 100 元,事务 T1 和事务 T2 都要扣除 30 元。在上面的实现中:
第一阶段,数据库的锁可以保证两个事务操作的并发安全,T2 在事务 T1 释放了数据库层面的资源锁以后,才可以继续操作。一阶段结束以后,数据库层面的资源锁会被释放。
第二阶段,通过业务隔离(预留)的方式为这部分资源加锁,不允许除本事务之外的其它并发事务动用。事务 T1 和 T2 都操作自己扣除的那一部分资源,相互之间无干扰。
这样,T1 和 T2 可以在同一个账户上并发执行。
TCC模式的优缺点
-
优点
1) 一阶段直接提交事务,释放数据库资源,性能好
2)相比AT模型,无需生成快照,无需使用全局锁,性能最强
3) 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
-
缺点
1)有代码侵入,需要人为的设计和编写try、Confirm和Cancel接口,太麻烦
2)软状态,事务是最终一致
3)需要考虑Confirm和Cancel的失败情况,做好幂等处理
3.5 saga模式
Saga模式是Seata提供的长事务解决方案。
Saga模式将一个长事务分解成可以交错运行的子事务集合,其中每个子事务都是一个真实的本地事务。如下图,一个长活事务由多个子事务Ti 组成,每个Ti 都有对应的补偿动作Ci,补偿动作用于撤销因Ti执行而造成的结果。
Saga模式的提交过程也分为两个阶段:
一阶段:直接提交子事务Ti
二阶段:成功则什么都不做;失败则执行补偿业务Ci来回滚;
和TCC相比,Saga没有“预留”动作,它的Ti就是直接提交到库。
saga模式的工作流程
Saga的执行顺序有两种:
- 向前恢复策略 forward recovery:T1, T2, T3, …, Tn
假设每个重试失败的事务,最终都会取得成功,适用于必须要成功的场景。
真正的执行顺序是类似于:T1, T2, …, Tj(失败), Tj(重试),…, Tn,其中j是发生错误的子事务。该情况下不需要Ci。 - 向后恢复策略 backward recovery:T1, T2, …, Tj, Cj,…, C2, C1,其中0 < j < n
如果任一子事务失败,则补偿所有已完成的事务。这种做法的效果是撤销掉之前所有成功的子事务,使得整个Saga的执行结果撤销。
Saga模式的优缺点
-
优点
1)事务参与者可以基于事件驱动实现异步调用,吞吐高
2)一阶段直接提交事务,无锁,性能好
3)不用编写TCC中的三个阶段,实现简单
-
缺点:
1)软状态持续时间不确定,时效性差
2)没有锁,没有业务层面的事务隔离,会有脏写
3.6 几种模式对比
XA | AT | TCC | SAGA | |
---|---|---|---|---|
一致性 | 强一致 | 弱一致 | 弱一致 | 最终一致 |
隔离性 | 完全隔离 | 基于全局锁隔离 | 基于资源预留隔离 | 无隔离 |
代码侵入 | 无 | 无 | 有,要编写三个接口 | 有,要编写状态机和补偿业务 |
性能 | 差 | 好 | 非常好 | 非常好 |
场景 | 对一致性、隔离性有高要求的业务 | 基于关系型数据库的大多数分布式事务场景 | 对性能要求较高的事务;有非关系型数据库要参与的事务。 | 业务流程长;参与者无法提供 TCC 模式要求的三个接口 |
参考: