关于java:XA-事务水很深小伙子我怕你把握不住

51次阅读

共计 6335 个字符,预计需要花费 16 分钟才能阅读完成。

@[toc]
分布式事务系列持续!

后面松哥和大家聊了 Seata 中的 TCC 模式以及 AT 模式,没看的小伙伴能够先看看:

  • 五分钟带你体验一把分布式事务!so easy!
  • 看了那么多博客,还是不懂 TCC,无妨看看这个案例!

明天咱们来持续学习 XA 事务!

Seata 中反对四种不同的事务模式:AT、TCC、XA 以及 Saga,这四种不同的事务模式中,XA 是最不同凡响的一个!为什么这么说,置信读完本文你就理解了。

1. 什么是 XA 标准

1.1 什么是两阶段提交

咱们先来略微回顾一下两阶段提交。

先来看上面一张图:

这张图里波及到三个概念:

  • AP:这个不必多说,AP 就是应用程序自身。
  • RM:RM 是资源管理器,也就是事务的参与者,大部分状况下就是指数据库,一个分布式事务往往波及到多个 RM。
  • TM:TM 就是事务管理器,创立分布式事务并协调分布式事务中的各个子事务的执行和状态,子事务就是指在 RM 上执行的具体操作。

那么什么是两阶段 (Two-Phase Commit, 简称 2PC) 提交?

两阶段提交说白了情理很简略,松哥举个简略例子来和大家阐明两阶段提交:

比方上面一张图(在五分钟带你体验一把分布式事务!so easy!一文中大家已经见过这张图):

在五分钟带你体验一把分布式事务!so easy!一文中,咱们在 Business 中别离调用 Storage 与 Order、Account,这三个中的操作要同时胜利或者同时失败,然而因为这三个分处于不同服务,因而咱们只能先让这三个服务中的操作各自执行,三个服务中的事务各自执行就是两阶段中的第一阶段。

第一阶段执行结束后,先不要急着提交,因为三个服务中有的可能执行失败了,此时须要三个服务各自把本人一阶段的执行后果报告给一个事务协调者(也就是后面文章中的 Seata Server),事务协调者收到音讯后,如果三个服务的一阶段都执行胜利了,此时就告诉三个事务别离提交,如果三个服务中有服务执行失败了,此时就告诉三个事务别离回滚。

这就是所谓的两阶段提交。

总结一下:两阶段提交中,事务分为参与者(例如上图的各个具体服务)与协调者(上文案例中的 Seata Server),参与者将操作成败告诉协调者,再由协调者依据所有参与者的反馈情报决定各参与者是要提交操作还是停止操作,这里的参与者能够了解为 RM,协调者能够了解为 TM。

不过 Seata 中的各个分布式事务模式,根本都是在二阶段提交的根底上演变进去的,因而并不齐全一样,这点须要小伙伴们留神。

1.2 什么是 XA 标准

XA 标准 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)规范。

XA 标准形容了全局的事务管理器与部分的资源管理器之间的接口。XA 标准的目标是容许的多个资源(如数据库,应用服务器,音讯队列等)在同一事务中拜访,这样能够使 ACID 属性逾越应用程序而放弃无效。

XA 标准应用两阶段提交来保障所有资源同时提交或回滚任何特定的事务。

XA 标准在上世纪 90 年代初就被提出。目前,简直所有支流的数据库都对 XA 标准提供了反对。

XA 事务的根底是两阶段提交协定。须要有一个事务协调者来保障所有的事务参与者都实现了筹备工作(第一阶段)。如果协调者收到所有参与者都筹备好的音讯,就会告诉所有的事务都能够提交了(第二阶段)。MySQL 在这个 XA 事务中表演的是参与者的角色,而不是协调者(事务管理器)。

MySQL 的 XA 事务分为外部 XA 和内部 XA。内部 XA 能够参加到内部的分布式事务中,须要应用层染指作为协调者;外部 XA 事务用于同一实例下跨多引擎事务,由 Binlog 作为协调者,比方在一个存储引擎提交时,须要将提交信息写入二进制日志,这就是一个分布式外部 XA 事务,只不过二进制日志的参与者是 MySQL 自身。MySQL 在 XA 事务中表演的是一个参与者的角色,而不是协调者。

2. MySQL 中的 XA

接下来松哥通过一个简略的例子先给大家看下 MySQL 中的 XA 是怎么玩的。

2.1 两阶段事务提交

比如说咱们上文中的转账操作,我用 MySQL 中的 XA 事务来和大家演示一下从一个账户直达出 10 块钱:

下面这段事务提交是一个两阶段事务提交的案例。

具体执行步骤如下:

  1. XA START "transfer_money":这个示意开启一个 XA 事务,前面的字符串是事务的 xid,这是一个惟一字符串,开启之后,事务的状态变为 ACTIVE
  2. update account set amount=amount-10 where account_no='A'; 这个示意执行具体的 SQL。
  3. XA END "transfer_money":这个示意完结一个 XA 事务,此时事务的状态转为 IDLE
  4. XA PREPARE "transfer_money":这个将事务置为 PREPARE 状态。
  5. XA COMMIT "transfer_money":这个用来提交事务,提交之后,事务的状态就是 COMMITED。

最初一步,能够通过 XA COMMIT 来提交,也能够通过 XA ROLLBACK 来回滚,回滚后事务的状态就是 ROLLBACK。

另外第四步能够省略,即一个 IDLE 状态的 XA 事务能够间接提交或者回滚。

咱们来看上面一张流程图:

从这张图里咱们能够看出,事务能够一步提交,也能够两阶段提交,都是反对的。如果是两阶段提交,prepare 之后,其实是在等其余的资源管理器(RM)反馈后果。

2.2 事务间接提交

松哥再给大家演示一下事务一步提交:

这个就比较简单,没啥好说的。

这块再跟大家介绍另外一个 XA 事务相干的命令 XA RECOVER,如下图:

XA RECOVER 能够列出所有处于 PREPARE 状态的 XA 事务,其余状态的事务则都不会列出来,如上图。

2.3 小结

在用一个客户端环境下,XA 事务和本地 (非 XA) 事务相互排挤,如果曾经通过 XA START 来开启一个事务,则本地事务不会被启动,直到 XA 事务被提交或者被回滚为止。

相同的,如果曾经应用 START TRANSACTION 启动一个本地事务,则 XA 语句不能被应用,直到该事务被提交或者回滚为止,而且 XA 事务仅仅被 InnoDB 存储引擎反对。

3. Seata 中的 XA

3.1 Seata 中的 XA 模式

咱们先来看一点理论知识,3.2 大节咱们再来看代码实际。

通过下面的介绍,大家曾经晓得了 MySQL 中的 XA 事务是怎么回事了,Seata 中的 XA 模式其实就是在 MySQL 中 XA 模式的根底上实现的。Seata 中的 XA 模式就是在 Seata 定义的分布式事务框架内,利用事务资源(数据库、音讯服务等)对 XA 协定的反对,以 XA 协定的机制来治理分支事务的一种事务模式。

咱们来看上面一张图:

我来大略说一下这个执行步骤:

  1. 首先由 TM 开启全局分布式事务。
  2. 各个业务 SQL 别离放在不同的 XA 分支中进行,具体执行的流程就是 XA Start->业务 SQL->XA End,这个流程跟我 2.1 大节和大家演示的 MySQL 中 XA 事务的流程是统一的。
  3. 分支中的 XA 事务执行实现后,执行 XA prepare,并将本人执行的状态报告给 TC。
  4. 其余的分支事务均依照 2、3 步骤来执行。
  5. 当所有分支事务都执行结束后,TC 也收到了各个分支事务报告上来的执行状态,如果所有状态都 OK,则 TC 告诉所有 RM 执行 XA Commit 实现事务的最终提交,否则 TC 告诉所有 RM 执行 XA Rollback 进行事务回滚。

这就是 Seata 中的 XA 模式!只有小伙伴们了解了 2.2 大节中 MySQL 的 XA 模式,那么 Seata 中的 XA 模式就很好了解了。

3.2 代码实际

接下来咱们来看一下代码实际,通过一个具体的案例来看下 Seata 中的 XA 模式。

这里咱们仍然是应用官网的案例,案例的业务和 AT 的业务是一样的,也是一个下单案例,如果小伙伴们对这个案例的业务不相熟,能够先看看五分钟带你体验一把分布式事务!so easy!一文。

不过还是我之前说的,官网案例中的依赖较多,下载容易出错,并且有些依赖存在版本抵触,须要自行解决之后能力运行起来,所以松哥这里本人也整顿了一套,如下:

小伙伴们能够在公众号后盾回复 seata-demo 下载这个案例,下载之后能够间接运行。

这里案例代码这块的问题,另外数据库这块也须要咱们提前准备下。

首先大家看到案例中有一个 sql 目录,这个目录下有对应的数据库脚本 all_in_one.sql,咱们在数据库中创立一个名为 seata_xa 的数据库,而后在数据库中执行该脚本。

执行实现后,会生成三张表:

和 AT 模式相比,这里就少了一张 undo_log 表,起因很简略,AT 二阶段回滚用的是反向弥补(通过更新语句将数据还原),而 XA 则是利用数据库本人的 XA 模式,通过 XA ROLLBACK 命令回滚的,所以 XA 模式不须要 undo_log 表。

接下来别离批改 account-xaorder-xastorage-xa 以及 business-xa 四个模块的 application.properties 配置文件,将数据库连贯地址改对,如下:

spring.datasource.url=jdbc:mysql:///seata_xa?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123

批改实现后,筹备工作就算做好啦,接下来就是我的项目启动了(business-xa 启动胜利后会主动实现数据的初始化)。

首先须要确保你的 seata-server 是启动状态的,seata-server 的启动还须要 eureka 的反对,这个大家具体能够参考五分钟带你体验一把分布式事务!so easy!一文。

而后别离启动 account-xaorder-xastorage-xa 以及 business-xa 模块。

我的项目启动胜利后,而后拜访如下地址实现一个购买操作:

http://localhost:8084/purchase

这是一个胜利的购买操作,即事务的二阶段会提交而不是回滚,拜访后浏览器响应如下:

接下来咱们能够增加一个拜访参数 count,示意购买的商品数量,这里设置要购买的商品数量为 999(实际上并没有这么多商品),因而在事务二阶段提交的时候会回滚。

http://localhost:8084/purchase?count=999

拜访后浏览器响应如下:

这就是通过 Seata XA 模式来解决分布式事务。

3.3 代码简析

可能有小伙伴感觉 XA 和 AT 很像,代码层面上的确很像!然而原理却差着十万八千里。

和 Seata AT 模式相比,Seata XA 模式次要是批改一下数据源,大家能够留神,account-xaorder-xa 以及 storage-xa 三个微服务的数据源都是 DataSourceProxyXA,以 order-xa 模式为例,数据源配置如下:

@Configuration
public class OrderXADataSourceConfiguration {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource druidDataSource() {return new DruidDataSource();
    }
    @Bean("dataSourceProxy")
    public DataSource dataSource(DruidDataSource druidDataSource) {
        // DataSourceProxy for AT mode
        // return new DataSourceProxy(druidDataSource);

        // DataSourceProxyXA for XA mode
        return new DataSourceProxyXA(druidDataSource);
    }
    @Bean("jdbcTemplate")
    public JdbcTemplate jdbcTemplate(DataSource dataSourceProxy) {return new JdbcTemplate(dataSourceProxy);
    }
}

能够看到,这里应用的数据源并非原生的数据源,而是通过包装后的 DataSourceProxyXA。

如果仅仅从代码层面来讲,把 DataSourceProxyXA 换成 DataSourceProxy 就是 AT 模式了,把 DataSourceProxy 换成 DataSourceProxyXA 就是 XA 模式了,就是这么简略!

这个案例中其余中央的代码基本上都是 AT 模式一样,所以我这里也就不啰嗦了。不过有一点须要留神,就是 business-xa 模块的数据源并没有应用 DataSourceProxyXA,起因在于该模块中配置数据源次要是为了初始化数据,并不波及分布式事务。

另外须要留神,在各个分支事务上,须要通过 @Transactional 注解来开启事务,开启之后,就会依照 XA 的那一套来,否则出问题了该服务不会回滚,默认状况下,只有 account-xa 模块加了该注解,小伙伴们在具体测试过程中,能够给其余模块也加上该注解,并进行测试查看成果。

4. XA 的几个问题

XA 模式有几个被人宽泛诟病的问题,咱们一起来理解下。

  1. 数据锁定

当应用 XA 事务时,数据在整个事务处理过程完结前,都被锁定,读写都按隔离级别的定义束缚起来。这的确是 XA 模式的一个劣势,不过这也是取得更高隔离性和全局一致性所要付出的代价。松哥后面和大家分享的弥补型事务处理机制(AT、TCC)尽管不存在这个问题,然而却就义了隔离性。AT 模式应用全局锁保障根本的写隔离,实际上也是锁定数据的,只不过锁在 TC 侧集中管理,解锁效率高且没有阻塞的问题。

  1. 协定阻塞

XA prepare 后,分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞期待。议的阻塞机制自身并不是问题,关键问题在于协定阻塞遇上数据锁定,如果一个参加全局事务的资源“失联”了(收不到分支事务完结的命令),那么它锁定的数据,将始终被锁定,进而可能因而产生死锁。这是 XA 协定的外围痛点,也是 Seata 引入 XA 模式要重点解决的问题,Seata 中次要是解决了失联问题,并通过减少自解锁机制来解决这个问题。

  1. 性能差

这可能是被诟病最多的中央了,XA 模式性能的损耗次要来自两个方面:一方面,事务协调过程,减少单个事务的 RT;另一方面,并发事务数据的锁抵触,升高吞吐。

和不应用分布式事务反对的运行场景比拟,性能必定是降落的,这点毫无疑问。实质上,事务(无论是本地事务还是分布式事务)机制就是拿局部 性能的就义,换来 编程模型的简略。与同为业务无侵入的 AT 模式比拟:

  • 同样运行在 Seata 定义的分布式事务框架下,XA 模式并没有产生更多事务协调的通信开销。
  • 并发事务间,如果数据存在热点,产生锁抵触,这种状况,在 AT 模式(默认应用全局锁)下同样存在的。

所以,在影响性能的两个次要方面,XA 模式并不比 AT 模式有非常明显的劣势。

AT 模式性能劣势次要在于:集中管理全局数据锁,锁的开释不须要 RM 参加,开释锁十分快;另外,全局提交的事务,实现阶段 异步化。

5. 总结

在以后的技术倒退阶段,不存一个分布式事务处理机制能够完满满足所有场景的需要。

一致性、可靠性、易用性、性能等诸多方面的零碎设计束缚,须要用不同的事务处理机制去满足。

Seata 我的项目最外围的价值在于:构建一个全面解决分布式事务问题的标准化平台。

基于 Seata,下层利用架构能够依据理论场景的需要,灵便抉择适合的分布式事务解决方案。

参考资料:

  1. https://seata.io/zh-cn/blog/s…
  2. https://www.cnblogs.com/duyan…
正文完
 0