Seata AT 模式 的工作原理是什么样的?

122 阅读3分钟

Seata 是一个开源的分布式事务解决方案,提供了 AT、TCC、SAGA 和 XA 事务模式,其中 AT 相对而言接入简单,代码侵入较低,对于非大型软件而言是一个不错的选择。本文将简述 AT 的工作原理。

  1. AT 实现的技术内容

    1. 它基于支持本地 ACID 事务的数据库,比如 MySQL 的 InnoDB 引擎,如果使用的数据库不支持 ACID 事务,那么 AT 也无法正常工作。在此基础上,AT 引入了全局锁机制,由独立安装的 Seata 服务器管理,称为 TC,全局锁相关的许多操作都由 TC 执行。

    2. 由 @GlobalTransactional 声明的方法,其下涉及到的多个微服务都将属于同一个全局事务。相关微服务的数据库操作会在本地事务的支持下执行,执行完毕后,在 commit 之前需要先获取全局锁

      1. 获取全局锁失败时,重试直到超时,全局事务内的所有写操作都需要回滚
      2. 获取全局锁成功后,执行 commit,本地事务结束。
      3. 如果其他微服务因为发生异常或者无法获取全局锁等,需要回滚,TC 会给相关的微服务发送消息,回滚之前提交的事务。
      4. 微服务回滚原来已经提交的本地事务,是需要从 undo_log 表中先获取并计算回滚语句的,这时候回滚过程也是一个本地事务;如果此时行锁被其他本地事务持有,那么回滚会失败,但是因为全局锁一直都在自己手上,其他本地事务因为一直获取不到全局锁而无法提交本地事务,等待超时后回滚;自己的回滚操作最终得以正常执行。
    3. 本地事务隔离级别为读已提交的基础上,全局事务的默认全局隔离级别是读未提交,也就是说允许其他全局事务读取到本全局事务尚未提交的内容(因为本地事务已经提交,后续开启的本地事务是能够读取到已经提交的数据的)

      1. 为了解决这个问题,AT 通过对 SELECT FOR UPDATE 语句的代理,实现了读已提交的隔离级别,它的实现原理还是通过全局锁进行的:每次执行完 SELECT FOR UPDATE 语句后,都要去获取全局锁,如果获取不到就放弃本次读取的内容,直到获取到全局锁或者超时后放弃。
      2. 这种解决办法会一定程度拖慢性能,为了避免对更多查询语句的影响,目前只有对 SELECT FOR UPDATE 语句进行代理,其他的 SELECT 不受影响。
  2. AT 工作机制

    1. 一阶段

      1. 查询前镜像(根据将要执行的 SQL 查询到对应的数据)
      2. 执行业务相关 SQL
      3. 查询后镜像(根据查询前镜像时获取到的数据,使用主键 id 直接查询)
      4. 写入 undo_log 表中,包含前镜像、后镜像、分支 id(Branch ID)、全局锁 id(XID)
      5. 申请全局锁后提交本地事务,上报 TC
    2. 二阶段提交

      1. 把提交请求放入异步任务队列中,马上上报 TC
      2. 异步任务将异步和批量地删除 undo_log 中对应的内容
    3. 二阶段回滚

      1. 通过 XID 和 Branch ID 找到对应的 undo_log 数据
      2. 根据 undo_log 的前镜像和业务 SQL 生成回滚语句并执行(如果后镜像的数据跟业务表中的数据不一样,说明已经被全局事务之外的事务更改过了,这时 AT 会做相应的处理)
      3. 提交本地事务,上报 TC

以上便是 Seata AT 模式的工作过程简述,了解后便能对开发过程中需要避哪些坑心中有数了。

更多细节请参考 Seata 的官方文档:seata.io/zh-cn/docs/…