Seata 是一个开源的分布式事务解决方案,提供了 AT、TCC、SAGA 和 XA 事务模式,其中 AT 相对而言接入简单,代码侵入较低,对于非大型软件而言是一个不错的选择。本文将简述 AT 的工作原理。
-
AT 实现的技术内容
-
它基于支持本地 ACID 事务的数据库,比如 MySQL 的 InnoDB 引擎,如果使用的数据库不支持 ACID 事务,那么 AT 也无法正常工作。在此基础上,AT 引入了全局锁机制,由独立安装的 Seata 服务器管理,称为 TC,全局锁相关的许多操作都由 TC 执行。
-
由 @GlobalTransactional 声明的方法,其下涉及到的多个微服务都将属于同一个全局事务。相关微服务的数据库操作会在本地事务的支持下执行,执行完毕后,在 commit 之前需要先获取全局锁
- 获取全局锁失败时,重试直到超时,全局事务内的所有写操作都需要回滚
- 获取全局锁成功后,执行 commit,本地事务结束。
- 如果其他微服务因为发生异常或者无法获取全局锁等,需要回滚,TC 会给相关的微服务发送消息,回滚之前提交的事务。
- 微服务回滚原来已经提交的本地事务,是需要从 undo_log 表中先获取并计算回滚语句的,这时候回滚过程也是一个本地事务;如果此时行锁被其他本地事务持有,那么回滚会失败,但是因为全局锁一直都在自己手上,其他本地事务因为一直获取不到全局锁而无法提交本地事务,等待超时后回滚;自己的回滚操作最终得以正常执行。
-
本地事务隔离级别为读已提交的基础上,全局事务的默认全局隔离级别是读未提交,也就是说允许其他全局事务读取到本全局事务尚未提交的内容(因为本地事务已经提交,后续开启的本地事务是能够读取到已经提交的数据的)
- 为了解决这个问题,AT 通过对 SELECT FOR UPDATE 语句的代理,实现了读已提交的隔离级别,它的实现原理还是通过全局锁进行的:每次执行完 SELECT FOR UPDATE 语句后,都要去获取全局锁,如果获取不到就放弃本次读取的内容,直到获取到全局锁或者超时后放弃。
- 这种解决办法会一定程度拖慢性能,为了避免对更多查询语句的影响,目前只有对 SELECT FOR UPDATE 语句进行代理,其他的 SELECT 不受影响。
-
-
AT 工作机制
-
一阶段
- 查询前镜像(根据将要执行的 SQL 查询到对应的数据)
- 执行业务相关 SQL
- 查询后镜像(根据查询前镜像时获取到的数据,使用主键 id 直接查询)
- 写入 undo_log 表中,包含前镜像、后镜像、分支 id(Branch ID)、全局锁 id(XID)
- 申请全局锁后提交本地事务,上报 TC
-
二阶段提交
- 把提交请求放入异步任务队列中,马上上报 TC
- 异步任务将异步和批量地删除 undo_log 中对应的内容
-
二阶段回滚
- 通过 XID 和 Branch ID 找到对应的 undo_log 数据
- 根据 undo_log 的前镜像和业务 SQL 生成回滚语句并执行(如果后镜像的数据跟业务表中的数据不一样,说明已经被全局事务之外的事务更改过了,这时 AT 会做相应的处理)
- 提交本地事务,上报 TC
-
以上便是 Seata AT 模式的工作过程简述,了解后便能对开发过程中需要避哪些坑心中有数了。
更多细节请参考 Seata 的官方文档:seata.io/zh-cn/docs/…