AT
模式是一种对业务无任何侵入的分布式事务解决方案。
先看官网对于AT模式的介绍:
整体机制
两阶段提交协议的演变:
-
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-
二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
什么是两段式提交:
一阶段:coordinator去提议voter1,voter2,voter3执行任务,但是不提交。
二阶段: voter1,voter2,voter3执行后,都没问题,一起提交。有问题,则回滚。
这样的问题是:二阶段出问题会直接挂掉。
一阶段voter1有问题,voter2,voter3都不可用。
那么AT模式如何改良的呢?
两阶段提交协议的演变:
-
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-
二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
在一阶段,解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
1.根据sql信息得到before image
2.执行业务sql.
3.根据sql信息得到after image
二阶段:执行成功,进行分布式事务提交
业务 SQL在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段:执行失败,进行业务回滚
首先对比“数据库当前业务数据”和 “after image”,
如果当前数据库数据如果和after image不一致,说明数据库已被修改,会提示报错,需人工介入。
如果当前数据库数据如果和after image一致,用“before image”执行update操作还原业务数据,并且删除中间数据。
数据第一阶段就提交,两个事务对于同一个表同一个字段操作时,如何避免脏写呢?
先看大前提:
1. 有全局锁,本地事务才能提交。
2.全局提交后,才释放全局锁。
3.拿全局锁有一定时间,超过时间,就不拿了,
因为有全局锁,本地事务才能提交。所以拿取超时的只能回滚并且释放本地锁。
例如两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行-10操作,m 的初始值 1000。
一阶段:
tx1 先开始,开启本地事务,拿到本地锁,
更新操作 m = 1000 - 100 = 900。
本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。
tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。
这时候tx2准备提交本地事务,但是tx1这时候还没有全局提交,tx1在全局提交前,一直持有全局锁,这时tx2一直尝试拿全局锁。
二阶段:
成功:tx1全局提交,释放全局锁;tx2拿到全局锁了,全局提交,释放全局锁。
失败: tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。 但是,这个时候,tx2还持有本地锁, 导致tx1回滚失败, 等tx2拿全局锁超时了,tx2回滚了,释放了本地锁,tx1才能回滚。
简单说来就是tx1全程拿着全局锁。我tx1不全局提交,你tx2也不能全局提交,tx2拿全局锁超时就回滚,所以就不会有脏写。