什么时候需要用到分布式事务:
就是指不是单个服务或者单个数据库架构下产生的事务,例如:
- 跨数据源的分布式事务
- 跨服务的分布式事务
Seata有四种模式: XA、AT(默认)、TCC、Seaga
XA:强一致性,基于数据库隔离,无代码侵入,在一阶段不提交事务
AT:默认模式,基于全局锁隔离,无代码侵入,一阶段提交事务,在提交事务前,会记录undolog日志,性能比XA模式好,二阶段TC通知回滚,则根据undolog回滚,通知提交,则删除undolog日志。
TCC:性能最好,不需要依赖关系型数据库,但代码入侵读高。Try:冻结可用数据,Confirm:确认提交数据,删除冻结数据 Canel:恢复数据,将冻结数据恢复
Seaga: 用于长事务,例如A项目调另外一个公司的项目接口。
解决分布式事务的理论基础
CAP定理:Consistency原子性、Availability(可用性)、Partition tolerance(分区容错性)
矛盾:在分布式事务中一定存在P,即分区容错性。如果要保证可用性,则分区的机器会出现数据不一致,即AP。如果要保证一致性,则分区的机器不能向外提供服务,即CP。
BASE理论:是解决CAP的一种思路
Basically Avaiable(基本可用): 分布式系统出现故障时,允许损失部分可用性,即保证核心可用。
Soft State(软状态):在一定时间内,允许出现中间状态,即短暂的数据不一致性。
Eventually Consistent(最终一致性):虽然无法保证强一致性,但在软状态结束后,最终达到数据一致性。
实战:
1、在服务调用方部署TC,注意,导入jar包后会自动寻找本地的seata服务
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本较低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<!--seata starter 采用1.4.2版本-->
<version>${seata.version}</version>
</dependency>
2、修改调用方配置文件,根据namespace -->group-->service-->cluster定位TC服务集群
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
namespace: "" # namespace,默认为空
group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
application: seata-tc-server # seata服务名称
username: nacos
password: nacos
tx-service-group: seata-demo # 事务组名称
service:
vgroup-mapping: # 事务组与cluster的映射关系
seata-demo: SH
Seata的XA模型:
RM一阶段:
1)TM开启全局事务
2)TM调用分支RM、RM将分支注册到TC、RM执行SQL(但不提交!)、RM将执行状态报告给TC
TC二阶段:
1)TM提交全局事务
2)TC统计各分支状态,如果都成功,则通知RM提交。如果失败,则通知RM回滚。
Seata AT模型:
一阶段:TM开启全局事务、TM调用分支、RM注册分支事务、RM记录undolog日志、RM提交事务、TCC记录各分支状态
二阶段:TM通知提交/回滚全局事务、TC检查各分支事务状态,成功,则删除undolog日志,失败,则根据undolog日志回滚。
脏写问题:
如果一个A事务执行sql并提交,另一个B事务也执行提交,此时A事务进行回滚,则会回滚为A记录的undolog日志,而B事务的更新修改记录会被忽略,出现了脏写问题。
解决:引入全局锁,在A事务提价事务释放DB锁之前,申请全局锁,而此时如果B事务进行操作修改,在执行更新数据库操作前会获取全局锁,获取失败,则无法更新,不断重试,但不能一直让其重试,否则A尝试获取B占用的DB锁则会造成死锁,一般让其重试30秒,然后失败则放弃其占有的DB锁,执行失败。A锁此时就能获取DB锁,执行回滚,然后再释放全局锁。
又引来新问题:如果是另一不归seata管理的事务的?全局锁失败!
XA也自动带来了解决的方案:
1)首先记录更新前的记录
2)记录更新后的记录。
完整正确的执行流程如下:
1)原数据假设为100,undolog记录100这个数值。
2)A事务获取DB锁将数据修改为90,此时undolog记录这条90。
3)A事务获取全局锁,并提交事务释放DB数据库锁
4)A事务回滚,在回滚为100前,会比较此时数据是否是90。假设,不归Seata管理的B事务,不需要获取全局锁,然后成功获取DB锁并修改了数据为80,则此时A事务将90(A修改后)与80(B修改)比较,则回滚失败。
Seata TCC模型:
Try: 判断是否有可用数据,足够则冻结可用数据。
Confirm: 完成资源的操作业务;要求try成功,confirm一定要成功。
Cancel: 预留资源释放,可以理解为try方向操作。
阶段1:检查资源是否足够,足够则冻结资源,执行try方法
阶段2:执行成功,则执行Confirm方法删除冻结资源。执行失败,执行Canel逻辑,恢复冻结资源
四种事务优缺点介绍:
XA:强一致性,无代码侵入、但一阶段事务不提交、会锁住资源,导致性能低。需要依赖数据库的事务特性。
AT:默认,弱一致性,无代码侵入,一阶段事务直接提交,失败则根据undolog日志回滚,隔离性引入全局锁,但并发几率低,所以性能会比XA好。
TCC:无需依赖关系型数据库,基于资源预留隔离。try、confirm、canel需要人工手写,而且需要考虑空悬挂、空回滚、幂等性判断,较为复杂、性能最好,但成本太高。
Seaga:适用于长事务类型,无太多应用场景。