什么是分布式事务?
分布式事务就是指不在单个服务或单个数据库架构下产生的事务问题。例如:
- 跨数据源的分布式事务
- 跨服务的分布式事务
1.1 CAP定理
解决分布式事务问题,需要一些分布式系统的基础知识作为理论指导,首先就是CAP定理。
1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标:
- Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致。
- Availability(可用性):用户访问分布式系统时,读或写操作总能成功。只能读不能写,或者只能写不能读,或者两者都不能执行,就说明系统弱可用或不可用。
- Partition tolerance (分区容错性):就是即使分布式系统节点之间出现网络故障导致节点之间无法通信,整个系统也能持续对外提供服务。
它们的第一个字母分别是 C、A、P。Eric Brewer认为任何分布式系统架构方案都不可能同时满足这3个目标,最多只能满足其中两个这个结论就叫做 CAP 定理。
1.2矛盾
在分布式系统中,网络不能100%保证畅通,也就是说网络分区的情况一定会存在。而我们的系统必须要持续运行,对外提供服务。所以分区容错性(P)是硬性指标,所有分布式系统都要满足。而在设计分布式系统时要取舍的就是一致性(C)和可用性(A)了。
由于网络故障,当我们把数据写入node01时,可以与node02完成数据同步,但是无法同步给node03。现在有两种选择:
- 允许用户任意读写,保证可用性。但由于node03无法完成同步,就会出现数据不一致的情况。满足AP
- 不允许用户写,可以读,直到网络恢复,分区消失。这样就确保了一致性,但牺牲了可用性。满足CP
所以CAP三个条件无法同时全部满足。
2. BASE理论
既然分布式系统要遵循CAP定理,那么问题来了,到底是该牺牲一致性还是可用性呢?如果牺牲了一致性,出现数据不一致该怎么处理? 人们在总结系统设计经验时,最终得到了一些心得:
- Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
- Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
- Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。 以上就是BASE理论。
BASE理论就是一种取舍的方案,不再追求完美,而是最终达成目标。因此解决分布式事务的思想也是这样,有两个方向:
- AP思想:各个子事务分别执行和提交,无需锁定数据。允许出现结果不一致,然后采用弥补措施恢复,实现最终一致即可。例如AT模式就是如此
- CP思想:各个子事务执行后不要提交,而是等待彼此结果,然后同时提交或回滚。在这个过程中锁定资源,不允许其它人访问,数据处于不可用状态,但能保证一致性。例如XA模式
3.Seata解决分布式事务的方案 XA 和 AT模式
Seata事务管理中有三个重要的角色:
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态
3.1 XA模式
一阶段的工作:
- RM注册分支事务到TC
- RM执行分支业务sql但不提交
- RM报告执行状态到TC
二阶段的工作:
- TC检测各分支事务执行状态
- 如果都成功,通知所有RM提交事务
- 如果有失败,通知所有RM回滚事务
- RM接收TC指令,提交或回滚事务
XA模式的优点是什么:
- 事务的强一致性,满足ACID原则。
- 常用数据库都支持,实现简单,并且没有代码侵入
XA模式的缺点是什么:
- 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务
3.2 AT模式
undo-log(数据快照)可采用数据库或redis实现
阶段一RM的工作:
- 注册分支事务
- 记录undo-log(数据快照)
- 执行业务sql并提交
- 报告事务状态
阶段二提交时RM的工作:
- 删除undo-log即可
- 阶段二回滚时RM的工作:
- 根据undo-log恢复数据到更新前
AT模式弥补了XA模型中资源锁定周期过长的缺陷,较常用
总结:
- XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
- XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
- XA模式强一致;AT模式最终一致
4.AT的脏写问题
再使用AT解决分布式事务时可能会出现脏写,假设一种情况,一个分支业务的SQL是这样的:update tb_account set money = money - 10 where id = 1,money金额为100,事务1获取DB锁,保存快照,然后执行sql后金额扣减为90,然后提交事务,然后事务2也要执行同样的sql扣减这条记录的金额,获取锁,扣减金额,提交事务,此时money=80。然后事务1所在的业务的其他事务没有执行成功,此时需要回滚,于是事务1使用之前保存的快照money=100恢复数据就会造成事务2的更新丢失,这就是AT的脏写问题。
解决思路就是引入了全局锁的概念。在释放DB锁之前,先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。
在这种模式下引入了全局锁,虽然其他事务在事务1的全局事务没有成功时也无法更新数据,从效果上看类似XA模式,但因为全局事务是由seata维护的因此对于该条数据仅时不可以写,但任然可以读,比数据库的写读,写写互斥锁会好一点。
若有没有被seata管理的事务也更新数据怎么办?
如上图所示,可以借助乐观锁的思想,在保存快照时,同时保存原始数据和执行sql之后的数据,当需要回滚时,首先将当前数据与之前保存的执行sql之后的快照进行对比,如果相同则回滚到原始快照,如果不相同则记录异常,发送金糕进行人工干预,因为出现这种问题肯定是程序员编码出现了问题,正常情况应该不能允许不在同一个全局事务的事务修改同一份数据。
5.TCC模式
TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复,不需加锁。需要实现三个方法:
- try:资源的检测和预留;
- confirm:完成资源操作业务;要求 try 成功 confirm 一定要能成功。
- cancel:预留资源释放,可以理解为try的反向操作。
事务悬挂和空回滚问题:
假如一个分布式事务中包含两个分支事务,try阶段,一个分支成功执行,另一个分支事务阻塞,如果阻塞时间太长,可能导致全局事务超时而触发二阶段的cancel操作。两个分支事务都会执行cancel操作,但是其中一个分支是未执行try操作的,直接执行了cancel操作,反而会导致数据错误。因此,这种情况下,尽管cancel方法要执行,但其中不能做任何回滚操作,这就是空回滚。 对于整个空回滚的分支事务,将来try方法阻塞结束依然会执行。但是整个全局事务其实已经结束了,因此永远不会再有confirm或cancel,也就是说这个事务执行了一半,处于悬挂状态,这就是业务悬挂问题。
总结
TCC模式的每个阶段是做什么的?
- Try:资源检查和预留
- Confirm:业务执行和提交
- Cancel:预留资源的释放
TCC的优点是什么?
- 一阶段完成直接提交事务,释放数据库资源,性能好
- 相比AT模型,无需生成快照,无需使用全局锁,性能最强
- 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
TCC的缺点是什么?
- 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
- 软状态,事务是最终一致
- 需要考虑Confirm和Cancel的失败情况,做好幂等处理、事务悬挂和空回滚处理
评论