Seata原理浅析
Seata原理浅析
Seata是阿里巴巴开源的分布式事务解决方案,旨在提供高性能和简单易用的分布式事务服务。本文将详细介绍Seata的事务模式、原理以及使用方法。
一、什么是 Seata
Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata为用户提供了XA、AT、TCC和SAGA事务模式,为用户打造一站式的分布式解决方案。
Seata的角色
- TC(Transaction Coordinator):事务协调者,用来协调全局和各个分支事务(不同服务)的状态,驱动它们的回滚或提交。
- TM(Transaction Manager):事务管理者,业务层中用来开启/提交/回滚一个整体事务(在调用服务的方法中用注解开启事务)。
- RM(Resource Manager):资源管理者,管理分支事务,与TC进行协调注册分支事务,并且汇报分支事务的状态,驱动分支事务的提交或回滚。
二、事务模式
1. XA 模式
Seata的XA模式大体与2PC事务相似。
1.1 流程介绍
第一阶段:
- RM注册分支事务到TC;
- RM执行分支业务的SQL但不提交;
- RM报告执行状态到TC;
第二阶段:
- TC检测检测各分支事务状态,判断整体事务提交或回滚;
- RM接受TC的指令,进行统一的提交或回滚操作。
1.2 XA 优缺点
优点:
- 事务强一致性,满足ACID原则;
- 实现简单,无代码入侵。
缺点:
- 一阶段锁定资源,二阶段结束才释放,性能较差;
- 依赖关系型数据库实现事务;
2. AT 模式
Auto Transaction,基于XA演进而来,需要数据库支持,如果是MySQL,则需要5.6以上版本才支持XA协议。
这是一种无侵入的分布式事务解决方案,该模式下,用户只需关注自己的业务SQL,Seata框架会在第一阶段拦截并解析SQL,生成undo log,并自动生成事务二阶段的提交和回滚操作。
AT模式下,是利用快照实现数据回滚,属于弱一致。
2.1 流程介绍
第一阶段:
- RM注册分支事务到TC;
- 记录undo log(数据快照);
- RM执行分支业务的SQL并提交;
- RM报告执行状态到TC;
第二阶段:
- TC检测检测各分支事务状态,判断整体事务提交或回滚;
- RM接受TC的指令,进行统一的提交或回滚操作。
- 提交时,异步删除相应分支的undo log;
- 回滚时,根据undo log生成补偿回滚的SQL,执行分支回滚并返回结果给TC;
例如,一个分支业务需要对account余额表中的money进行扣减10元,则需要进行如下流程:
2.2 脏写问题
并发事务之间,可能会产生脏写导致数据修改被覆盖。Seata通过全局锁来管理事务,持有全局锁的事务才有执行SQL的权利,这里全局锁只针对交由Seata管理的事务。
如下图,简单流程大致如下:
- 一阶段本地事务提交前,需要确保先拿到全局锁;
- 拿不到全局锁,不能提交本地事务;
- 拿不到全局锁会重试,次数有限,超出限制将放弃,并回滚本地事务,释放本地锁。
2.3 数据快照
那么非Seata事务于Seata事务并发修改数据时如何处理?
RM在第一阶段将分支事务注册到TC时,会在undo log保存两个数据快照,分别是:
- before-image:数据修改前的快照
- after-image:数据修改后的快照
当发生异常时,before-image用来做数据回滚,after-image用来判断修改后数据于当前数据是否相同,相同则通过before-image做数据回滚,不同则说明被其他非Seata事务修改过,记录异常,人工介入。
具体流程见下图。
2.4 脏读问题
与脏写类似,是指在全局事务未提交前,被其它业务读到已提交的分支事务的数据,本质上Seata默认的全局事务是读未提交。
那么怎么避免脏读现象呢?
- 业务查询时要使用@GlobalTransactional或@GlobalLock来修饰查询方法的调用;
- 查询语句须使用select for update语句。
这样在执行SQL前会检查全局锁是否存在,只有当全局锁完成之后,才能继续执行SQL,这样就防止了脏读。
不过,AT事务模式下读已提交的成本很高,对于非必要场景还是要尽量避免使用。
传统的读已提交不需要本地锁,但这里却需要select for update语句,查询多出了加锁和竞争的开销,另外还要持锁调用TC的lockQuery接口以判断全局锁情况。
2.5 AT 优缺点
优点:
- 一阶段直接完成事务提交,释放数据库资源,性能比较好;
- 利用全局锁实现读写隔离;
- 没有代码入侵,框架自动完成回滚或提交。
缺点:
- 两阶段之间属于软状态,属于最终一直;
- 数据快照会影响性能,但比XA模式要好很多;
3. TCC 模式
关于什么是TCC模式及原理,详情见什么是分布式事务。
TCC与AT模式很相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。
3.1 流程介绍
TCC每个阶段是做什么的:
- Try:资源的检测和预留;
- Confirm:完成资源操作业务,要求Try成功,Confirm一定能成功;
- Cancel:预留资源释放,可以理解为Try的反向操作。
TCC不存在资源阻塞的问题,因为每个方法都直接进行事务的提交,一旦出现异常通过则Cancel来进行回滚补偿,这也就是常说的补偿性事务
举例,一个扣减用户愈合的业务,假设账户A原来的余额是100,需要扣减30元。
空回滚和业务悬挂
什么是空回滚?
分支事务Try操作阻塞时,可能导致全局事务超时触发Cancel操作。在Try未执行时先执行了Cancel,这时的Cancel理论上不应该回滚,这时就需要空回滚。
什么是业务悬挂?
对于已经空回滚的业务,这时如果线程不再阻塞,继续执行Try,但不可能Confirm或Cancel,这就是业务悬挂,需要避免空回滚后的Try操作。
如何解决空回滚和业务悬挂?
回滚时需要在执行Cancel操作时,判断有没有执行Try操作。相应的,在执行Try时判断有没有该事务是否回滚过。
这里,我们假设需要在冻结金额的时候进行事务操作。为了实现空回滚,防止业务悬挂,以及幂等性要求。我们必须在数据库记录冻结金额的同时,记录当前事务ID和执行状态,冻结金额表如下设计:
CREATE TABLE 'account_freeze_tbl'(
'xid' varchar (128) NOT NULL,
'user_id' varchar(255) DEFAULT NULL COMMENT '用户id',
'freeze_money' int(11) unsigned DEFAULT '0' COMMENT '冻结金额',
'state' int(1) DEFAULT NULL COMMENT '事务状态, O:try, 1:confirm, 2:cancel',
PRIMARY KEY ('xid') USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
表字段设计完成后,执行如下的业务逻辑即可避免空回滚和业务悬挂。
3.2 TCC 优缺点
优点:
- 一阶段直接完成事务提交,释放数据库资源,性能比较好;
- 相比AT,无需生成快照和使用全局锁,性能最好;
- 不依赖数据库事务,依赖补偿操作,可用于非事务型数据库。
缺点:
- 代码入侵,每个阶段都需要编写对应的业务代码;
- 软状态,属于最终一致;
- 需要考虑Confirm和Cancel的失败情况,做好幂等处理。
4. Saga 模式
关于什么是Saga模式及原理,详情见什么是分布式事务。
Saga模式是Seata提供的长事务解决方案。也分为两个阶段:
- 一阶段:直接提交本地事务
- 二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚
优点:
- 事务参与者可以基于事件驱动实现异步调用,吞吐高;
- 一阶段直接提交本地事务,无锁,性能好;
- 代码入侵较TCC低,实现简单。
缺点:
- 软状态持续时间不确定,时效性差;
- 没有锁和事务隔离,可能会有脏写。
三、代码实现
具体代码使用,可参考Seata官方文档。
这里需要注意每个模式需要的准备工作不同,如AT模式下就需要准备如下几点:
- lock_table导入Seata数据库,就是TC服务关联的数据库;
- undo_log导入业务相关的数据库;
- 修改事务模式。
四、对比总结
对比维度 | XA | AT | TCC | Saga |
---|---|---|---|---|
数据一致性 | 强一致性 | 弱一致性 | 最终一致性 | 最终一致性 |
隔离性 | 完全隔离 | 基于全局锁 | 基于资源预留 | 无隔离 |
代码入侵 | 无 | 无 | 有 | 有 |
性能 | 低 | 较低 | 中 | 高 |
依赖本地事务 | 依赖 | 依赖 | 不依赖 | 不依赖 |
场景 | 一致性,隔离性要求高的业务场景。 | 继续关系型数据库的大多分布式事务的场景均适合。 | 对性能要求高,且有非关系型数据库参与的事务。 | 业务流程较长,数据时效性要求较低的场景。 |
参考:
- B站黑马. Seata从入门到进阶.