Seata简介
Seata 是一款开源的分布式事务解决方案,致力于提供高性能与简单易用的分布式事务服务,为用户提供了 AT、TCC、SAGA 和 XA 几种不同的事务模式:
-
AT模式:无侵入式的分布式事务解决方案,适合不希望对业务进行改造的场景,但由于需要添加全局事务锁,对影响高并发系统的性能。该模式主要关注多DB访问的数据一致性,也包括多服务下的多DB数据访问一致性问题
-
TCC模式:高性能的分布式事务解决方案,适用于对性能要求比较高的场景。该模式主要关注业务拆分,在按照业务横向扩展资源时,解决服务间调用的一致性问题
-
Saga模式:长事务的分布式事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统。Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统,事务参与者可以是其它公司的服务也可以是遗留系统的服务,并且对于无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式
Seata 的核心组件:
-
事务协调器(TC):维护全局事务的运行状态,负责协调并驱动全局提交或回滚
-
TC 作为 Seata 的服务端独立部署
-
-
事务管理器(TM):事务发起方,控制全局事务的范围,负责开启一个全局事务,并最终发起全局提交或回滚全局的决议
-
作为 Seata 的客户端
-
-
资源管理器(RM):事务参与方,管理本地事务正在处理的资源,负责向 TC 注册本地事务、汇报本地事务状态,接收 TC 的命令来驱动本地事务的提交或回滚
-
作为 Seata 的客户端
-
XA模式(强一致性)
XA早期版本是一种规范 主流数据库对XA规范提供了支持,没有TM(事务管理者的概念)只有TC和RM
Seata中的XA实现原理多了TM的概念
一阶段不提交事务,在第二阶段TM发起事务提交/回滚时才会让TC检查分支状态做提交/回滚因此是强一致性的
优点:
-
强一致性
-
易于使用:因为主流数据库都支持且无代码侵入
缺点:
-
第一阶段不提交,在等待过程中占用数据库锁,占用系统资源,性能差
Seata 的 AT 模式原理:(最终一致性)
Seata AT模式是基于XA事务(XA是基于数据库实现的分布式事务协议)演进而来,需要数据库支持,如果是 MySQL,则需要5.6以上版本才支持XA协议。AT 模式的特点就是对业务无入侵式,用户只需要关注自己的业务SQL,Seata 框架会在第一阶段拦截并解析用户的 SQL,并保存其变更前后的数据镜像,形成undo log,并自动生成事务第二阶段的提交和回滚操作。
AT 模式的整体执行流程:
图片源于“黑马的Seata课程”B站上可以找到虎哥讲的,建议去听非常通俗易懂。
第一阶段的详细执行流程:
在第一阶段,RM 写表时,Seata 通过代理数据源(从而达到对业务无侵入的效果)拦截业务 SQL 并 解析 SQL 语义,找到该 SQL 要更新的业务数据保存成 before image(前置镜像),然后执行业务SQL,在业务数据更新后,再将其保存成 after image(后置镜像),最后向 TC 注册全局锁信息(这个“锁”并非数据库加锁,而是 Seata 的逻辑控制机制,用于全局事务的并发冲突检测,锁的单位是整行:表名 + 主键标识)。通过把更新前后的业务数据数据镜像组织成回滚日志 undo log,并利用本地事务的 ACID 特性,将业务数据的更新和回滚日志 undo log 的写入在同一个本地事务中提交,保证任何提交的业务数据的更新一定有相应的回滚日志存在(即操作的原子性)。
基于这样的机制,分支的本地事务就可以在全局事务的第一阶段提交,并马上释放本地事务锁定的资源,这也是 Seata 的 AT 模式和 XA 事务的不同之处,两阶段提交往往对资源的锁定需要持续到第二阶段实际的提交或者回滚操作,而有了回滚日志之后,可以在第一阶段释放对资源的锁定,降低了锁范围,提高效率,即使第二阶段发生异常需要回滚,只需找对 undo log 中对应数据镜像并反解析成 SQL 来达到回滚目的
第二阶段提交的详细执行流程:
如果二阶段的全局表决结果是提交的话,说明所有分支事务的业务SQL已经在第一阶段生效,此时 Seata 框架只需异步删除所有分支第一阶段保存的镜像数据、回滚日志和行锁,完成数据清理即可,这个过程是非常快速的
第二阶段回滚的详细执行流程:
如果第二阶段是回滚的话,Seata 就需要回滚第一阶段已执行的 SQL 进行还原业务数据。由 TC 通知所有 RM 进行根据第一阶段的回滚日志 undo log (即before image)进行反向补偿,RM 收到 TC 发来的回滚请求后,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过undo log 生成反向的更新 SQL 并执行,以完成分支的业务数据还原,最后删除 undo log、redo log 和行锁。但在还原前需要校验脏写,对比数据库”当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
AT模式的写隔离
Seata引入全局锁(预防Seata管控的数据源脏写):由TC记录当前正在操作的某行数据事务,该事务持有全局锁,具备执行权。
注意:全局锁并非数据库的全局锁,而是一个逻辑锁可以存放在Mysql、Redis中,锁结构如下图:
图片源于“黑马的Seata课程”B站上可以找到虎哥讲的,建议去听非常通俗易懂。
如果不受Seata管理的业务去修改数据因为没有全局锁的管控在一阶段提交之后依然可以修改数据
Seata为此也做了准备:引入更新前快照和更新后快照
-
更新前快照:用来回滚
-
更新后快照:用来对比在二阶段提交时是否有人修改(为不受Seata代理的数据源做兜底)
非Seata管控的业务依然存在脏写问题场景
图片源于“黑马的Seata课程”B站上可以找到虎哥讲的,建议去听非常通俗易懂。
总结Seata的AT模式
Seata AT模式为什么要校验脏写?
因为 AT 模式的第一阶段是“自动提交本地事务”的!
为了避免如果有人没走代理数据源偷偷改了这条记录(也就是没有使用Seata),Seata 就能检测出来。(兜底方案)
如何避免脏写?
-
使用 Seata 的代理数据源,让所有数据更新都受 Seata 控制;
-
保证分布式事务相关的其他本地事务也受 Seata 管控;
-
业务上尽量避免并发修改相同行;
-
或者使用 TCC 模式 / SAGA 模式,更细粒度控制资源。
AT模式优缺点
优点:
-
一阶段完成直接提交事务,释放数据库资源,性能比较好
-
利用全局锁实现读写隔离
-
没有代码侵入,框架自动完成回滚和提交
缺点:
-
两阶段之间属于软状态,属于最终一致
-
框架的快照功能会影响性能,但比XA模式要好很多
AT模式和XA模式的区别:
-
XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源
-
XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
-
XA模式强一致;AT模式最终一致
TCC 模式原理(最终一致性)
图片源于“黑马的Seata课程”B站上可以找到虎哥讲的,建议去听非常通俗易懂。
TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。
TCC 三个方法描述:
-
Try:资源的检测和预留;
-
Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
-
Cancel:预留资源释放;
TCC 设计 - 业务模型分 2 阶段设计:
我先说明一下这里只讲理论,想要真正理解TCC模式是需要真实编码的还是那句话看“黑马的Seata课程”
可以看明白就往下看,看不明白直接看总结
用户接入 TCC ,最重要的是考虑如何将自己的业务模型拆成两阶段来实现。
以“扣钱”场景为例,在接入 TCC 前,对 A 账户的扣钱,只需一条更新账户余额的 SQL 便能完成;但是在接入 TCC 之后,用户就需要考虑如何将原来一步就能完成的扣钱操作,拆成两阶段,实现成三个方法,并且保证一阶段 Try 成功的话 二阶段 Confirm 一定能成功。
如上图所示,Try 方法作为一阶段准备方法,需要做资源的检查和预留。在扣钱场景下,Try 要做的事情是就是检查账户余额是否充足,预留转账资金,预留的方式就是冻结 A 账户的 转账资金。Try 方法执行之后,账号 A 余额虽然还是 100,但是其中 30 元已经被冻结了,不能被其他事务使用。
二阶段 Confirm 方法执行真正的扣钱操作。Confirm 会使用 Try 阶段冻结的资金,执行账号扣款。Confirm 方法执行之后,账号 A 在一阶段中冻结的 30 元已经被扣除,账号 A 余额变成 70 元 。
如果二阶段是回滚的话,就需要在 Cancel 方法内释放一阶段 Try 冻结的 30 元,使账号 A 的回到初始状态,100 元全部可用。
用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。
TCC 设计 - 允许空回滚:(在执行Cancel 之前判断有没有执行过Try,需要记录执行状态)
如上图场景分析先触发了二阶段cancel操作,其实解决也很简单,根据事务ID去查询我们创建的预留表在业务代码中判断是否为null
@Override
public boolean cancel(BusinessActionContext context) {
//查询用户记录
String xid = context.getXid(); //事务ID
String userId = context.getActionContext("userId").toString();
AccountFreeze accountFreeze = accountFreezeMapper.selectById(xid);
// 空回滚判断 判断freeze为null证明try没执行需要空回滚
if (accountFreeze == null) {
// 执行空回滚
accountFreeze = new AccountFreeze();
accountFreeze.setUserId(userId);
accountFreeze.setFreezeMoney(0);
accountFreeze.setXid(xid);
accountFreeze.setState(AccountFreeze.State.CANCEL);
accountFreezeMapper.insert(accountFreeze);
return true;
}
// 判断幂等
if (accountFreeze.getState() == AccountFreeze.State.CANCEL) {
// 以及处理过一次cancel无需重复处理
}
// 恢复可用余额
accountMapper.refund(accountFreeze.getUserId(),accountFreeze.getFreezeMoney());
// 将冻结金额清零 状态改为cancel
accountFreeze.setState(AccountFreeze.State.CANCEL);
accountFreeze.setFreezeMoney(0);
int count = accountFreezeMapper.updateById(accountFreeze);
return count==1;
}
TCC 设计 - 防悬挂控制:(在执行Try之前判断有没有执行过Cancel ,需要记录执行状态)
悬挂的意思是:Cancel 比 Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而最终又收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作
TCC 设计 - 幂等控制:
幂等性的意思是:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。因为网络抖动或拥堵可能会超时,事务管理器会对资源进行重试操作,所以很可能一个业务操作会被重复调用,为了不因为重复调用而多次占用资源,需要对服务设计时进行幂等控制,通常我们可以用事务 xid 或业务主键判重来控制。
总结Seata的TCC模式
并不是所有业务都适合TCC模式,例如新增订单就不适合,更适用于修改业务不过Seata是可以模式混合使用的
优点:
-
一阶段完成直接提交事务,释放数据库资源,性能好
-
相比AT模型,无需生成快照,无需使用全局锁,性能最强
-
不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
-
Redis这种也可以使用TCC模式
-
缺点:
-
有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
-
软状态,事务是最终一致
-
需要考虑Confirm和Cancel的失败情况,做好幂等处理
-
因为如果失败Seata会重试,所有要做好幂等
-
Saga模式
在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。
Saga 模式使用场景
Saga 模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。
Saga模式的优势是:
-
一阶段提交本地数据库事务,无锁,高性能;
-
参与者可以采用事务驱动异步执行,高吞吐;
-
补偿服务即正向服务的“反向”,易于理解,易于实现;
缺点:Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。后续会讲到对于缺乏隔离性的应对措施。
Saga 模式的实现
目前 Saga 的实现一般也两种,一种是通过事件驱动架构实现,一种是基于注解加拦截器拦截业务的正向服务实现。Seata 目前是采用事件驱动的机制来实现的,Seata 实现了一个状态机,可以编排服务的调用流程及正向服务的补偿服务,生成一个 json 文件定义的状态图,状态机引擎驱动到这个图的运行,当发生异常的时候状态机触发回滚,逐个执行补偿服务。当然在什么情况下触发回滚用户是可以自定义决定的。该状态机可以实现服务编排的需求,它支持单项选择、并发、异步、子状态机调用、参数转换、参数映射、服务执行状态判断、异常捕获等功能。
状态机引擎原理
该状态机引擎的基本原理是,它基于事件驱动架构,每个步骤都是异步执行的,步骤与步骤之间通过事件队列流转,极大的提高系统吞吐量。每个步骤执行时会记录事务日志,用于出现异常时回滚时使用,事务日志会记录在与业务表所在的数据库内,提高性能。
状态机引擎设计
该状态机引擎分成了三层架构的设计,最底层是“事件驱动”层,实现了 EventBus 和消费事件的线程池,是一个 Pub-Sub 的架构。第二层是“流程控制器”层,它实现了一个极简的流程引擎框架,它驱动一个“空”的流程执行,“空”的意思是指它不关心流程节点做什么事情,它只执行每个节点的 process 方法,然后执行 route 方法流转到下一个节点。这是一个通用框架,基于这两层,开发者可以实现任何流程引擎。最上层是“状态机引擎”层,它实现了每种状态节点的“行为”及“路由”逻辑代码,提供 API 和状态图仓库,同时还有一些其它组件,比如表达式语言、逻辑计算器、流水生成器、拦截器、配置管理、事务日志记录等。
Saga 服务设计需要解决的问题
和TCC类似,Saga的正向服务与反向服务也需求遵循以下设计原则:
Saga 服务设计 - 允许空补偿
Saga 服务设计 - 防悬挂控制
Saga 服务设计 - 幂等控制
Saga 设计 - 自定义事务恢复策略
Saga 模式不保证事务的隔离性,在极端情况下可能出现脏写。比如在分布式事务未提交的情况下,前一个服务的数据被修改了,而后面的服务发生了异常需要进行回滚,可能由于前面服务的数据被修改后无法进行补偿操作。这时的一种处理办法可以是“重试”继续往前完成这个分布式事务。由于整个业务流程是由状态机编排的,即使是事后恢复也可以继续往前重试。所以用户可以根据业务特点配置该流程的事务处理策略是优先“回滚”还是“重试”,当事务超时的时候,Server 端会根据这个策略不断进行重试。
由于 Saga 不保证隔离性,所以我们在业务设计的时候需要做到“宁可长款,不可短款”的原则,长款是指在出现差错的时候站在我方的角度钱多了的情况,钱少了则是短款,因为如果长款可以给客户退款,而短款则可能钱追不回来了,也就是说在业务设计的时候,一定是先扣客户帐再入帐,如果因为隔离性问题造成覆盖更新,也不会出现钱少了的情况。
基于注解和拦截器的 Saga 实现
还有一种 Saga 的实现是基于注解+拦截器的实现,Seata 目前没有实现,可以看上面的伪代码来理解一下,one 方法上定义了 @SagaCompensable 的注解,用于定义 one 方法的补偿方法是 compensateOne 方法。然后在业务流程代码 processA 方法上定义 @SagaTransactional 注解,启动 Saga 分布式事务,通过拦截器拦截每个正向方法当出现异常的时候触发回滚操作,调用正向方法的补偿方法。
两种 Saga 实现优劣对比
状态机引擎的最大优势是可以通过事件驱动的方法异步执行提高系统吞吐,可以实现服务编排需求,在 Saga 模式缺乏隔离性的情况下,可以多一种“向前重试”的事情恢复策略。注解加拦截器的的最大优势是,开发简单、学习成本低。
总结
Seata 的定位是分布式事全场景解决方案,未来还会有 XA 模式的分布式事务实现,每种模式都有它的适用场景:
AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎 0 学习成本。
TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。
Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。
文章引用
探秘蚂蚁金服分布式事务 Seata 的AT、Saga和TCC模式-CSDN博客
黑马“Seata课程”B站视频截图