乐趣区

关于golang:深度剖析分布式事务之-AT-与-XA-对比

AT 这种事务模式是阿里开源的 seata 主推的事务模式,本文会详解 AT 的原理,并将它与 XA 模式进行比拟

原理

AT 从原理下面看,与 XA 的设计有很多相近之处。XA 是数据库层面实现的二阶段提交,AT 则是利用 / 驱动层实现的二阶段提交。建议您理解了 XA 相干的常识后,来浏览这篇文章,这样可能更快更好的把握 AT 的原理与设计。

AT 的角色和 XA 一样分为 3 个,然而起了不一样的名称,大家留神分辨:

  • RM 资源管理器,是业务服务,负责本地数据库的治理,与 XA 中的 RM 统一
  • TC 事务协调器,是 Seata 服务器,负责全局事务的状态治理,负责协调各个事务分支的执行,相当于 XA 中的 TM
  • TM 事务管理器,是业务服务,负责全局事务的发动,相当于 XA 中的 APP

AT 的第一阶段为 prepare,它在这一阶段会实现以下事件:

  1. RM 侧,用户开启本地事务
  2. RM 侧,用户每进行一次业务数据批改,假如是一个 update 语句,那么 AT 会做以下内容:

    1. 依据 update 的条件,查问出批改前的数据,该数据称为 BeforeImage
    2. 执行 update 语句,依据 BeforeImage 中的主键,查问出批改后的数据,该数据称为 AfterImage
    3. 将 BeforeImage 和 AfterImage 保留到一张 undolog 表
    4. 将 BeforeImage 中的主键以及表名,该数据称为 lockKey,记录下来,留待后续应用
  3. RM 侧,用户提交本地事务时,AT 会做以下内容:

    1. 将 2.4 中记录的所有的 lockKey,注册到 TC(即事务管理器 seata)上
    2. 3.1 中的注册解决会查看 TC 中,是否已存在抵触的主键 + 表名,如果有抵触,那么 AT 会睡眠期待后重试,没有抵触则保留
    3. 3.1 胜利实现后,提交本地事务

如果 AT 的第一阶段所有分支都没有谬误,那么会进行第二阶段的 commit,AT 会做以下内容:

  1. TC 会将以后这个全局事务所有相干的 lockKey 删除
  2. TC 告诉与以后这个全局事务相干的所有业务服务,告知全局事务已胜利,能够删除 undolog 中保留的数据
  3. RM 收到告诉后,删除 undolog 中的数据

如果 AT 的第一阶段有分支出错,那么会进行第二阶段的 rollback,AT 会做以下内容:

  1. TC 告诉与以后这个全局事务相干的所有业务服务,告知全局事务失败,执行回滚
  2. RM 收到告诉后,对本地数据的批改进行回滚,回滚原理如下:

    1. 从 undolog 中取出批改前后的 BeforeImage 和 AfterImage
    2. 如果 AfterImage 与数据库中的以后记录校验统一,那么应用 BeforeImage 中的数据笼罩以后记录
    3. 如果 AfterImage 与数据库中的以后记录不统一,那么这个时候产生了 脏回滚,此时须要人工染指解决
  3. TC 待全局事务所有的分支,都实现了回滚,TC 将此全局事务所有的 lockKey 删除

问题剖析

AT 模式的一个突出问题是 rollback 中 2.3 的脏回滚难以避免。以下步骤可能触发该脏回滚:

  1. 全局事务 g1 对数据行 A1 进行批改 v1 -> v2
  2. 另一个服务将对数据行 A1 进行批改 v2 -> v3
  3. 全局事务 g1 回滚,发现数据行 A1 的以后数据为 v3,不等于 AfterImage 中的 v2,回滚失败

这个脏回滚一旦产生,那么分布式事务框架没有方法保证数据的一致性了,必须要人工染指解决。想要防止脏回滚,须要把所有对这个表的写访问,都加上非凡解决(在 Seata 的 Java 客户端中,须要加上 GlobalLock 注解)。这种束缚对于一个上了肯定规模的简单零碎,是十分难以保障的。

AT vs XA

上述脏回滚问题,在 XA 事务中不会呈现,因为 XA 事务是在数据库层面实现的,当另一个服务对为数据行 A1 进行批改时,会因为行锁被阻塞,与一般事务的体现齐全一样,不会产生问题。

另外 XA 不会产生脏读,而 AT 会产生脏读,思考 AT 下的如下执行步骤:

  1. 全局事务 g1 对数据行 A1 进行批改 v1 -> v2
  2. 另一个服务将读取数据行 A1,取得数据 v2
  3. 全局事务 g1 回滚,将数据行 A1 改回 v2 -> v1

这外面步骤 2 读取的数据是 v2,是一个两头态数据。在 Seata 的手册中,尽管也有一些办法可能防止 AT 模式下,然而波及到注解和 sql 改写,并不优雅。而在 XA 模式下,因为还没有进行 xa commit,那么步骤 2 依据 MVCC 读取到的数据仍然是 v1,没有 AT 模式中的脏读的困扰。

性能剖析

从原理的具体步骤看,XA 事务的性能高于 AT,剖析如下:

AT 模式下,RM 侧,上述原理过程中,执行的 SQL 如下:

  1. 开启事务
  2. 查问 BeforeImage 数据
  3. 执行 update
  4. 查问 AfterImage 数据
  5. 将 BeforeImage,AfterImage 插入到 undolog 中
  6. 提交事务
  7. 事务实现后,删除 BeforeImage 和 AfterImage

而 XA 模式下,RM 侧,执行的 SQL 如下:

  1. xa begin
  2. 执行 update
  3. xa end
  4. xa prepare
  5. xa commit

两者比照,相干的开启 / 提交事务是两个模式都须要的,性能差别不大。然而从执行的 DML 操作来看,AT 下的 SQL 数量为:3 writes,2 read,比 XA 下仅一个 update 多出许多,因而在性能上会有较大的差距

从上述实践剖析,XA 事务性能会大幅高于 AT,该当能够在 postgres 数据库上验证进去;而 mysql 数据库,在以后的 5.8 版本上,因为 xa prepare 后,须要将以后连贯断开才可能在其余连贯上 xa commit,所以会有一个从新创立连贯的开销,最终性能比照参考下一节。

性能实测

上述进行了实践上的性能剖析,我同时也做了性能实测,具体的测试过程和后果数据,参考 xa-at bench

dtm 实现的 XA 事务,为了在极其状况下,也能保障 XA 事务可能正确的被清理,会在业务事务中对子事务屏障表进行插入,因而会比上述实践剖析中,多一个 sql 写入。

咱们能够看到,最终的后果 XA 性能优于 AT。如果将来 Mysql 欠缺了 XA 的实现,能够不必敞开以后连贯也可能容许其余连贯提交 xa 事务,那么 XA 的性能还可能晋升一大截。

AT 的意义

mysql 在版本 5.6 中,xa 相干 API 存在 bug。如果以后连贯在 xa prepare 之后,连贯断开,那么这个连贯未实现的事务会被主动回滚。这样的 bug 导致 mysql 的 XA 模式是无奈保障正确性的,在各种利用 crash 中,可能导致数据不统一。因而 AT 在 mysql 的 5.6 版本及更低版本应用中,是具备很高利用价值的。

另外局部大厂的数据库是禁止应用 XA 事务的,这种特定场景下,选型 AT 模式,也是正当的。

对于其余场景,倡议优先思考 XA 事务。

小结

作者对 AT 模式的残缺实现源码,并未残缺浏览。上述的相干原理是依据本人浏览相干材料,并参考了 seata-golang 的源代码而写。文中如果不精确之处,心愿各位读者帮忙斧正

欢送拜访 https://github.com/dtm-labs/dtm 并 star 反对咱们

退出移动版