关于seata:分布式事务原理简单写起来全是坑

30次阅读

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

分布式事务,咱们曾经给小伙伴介绍了整体内容:

  • 一文搞明确分布式事务解决方案!真的 so easy!

AT 模式的实现:

  • 手把手率领小伙伴们写一个分布式事务案例!

AT 模式在多数据源中的利用:

  • Spring Boot 多数据源如何处理事务?教你一招!

TCC 模式的实现:

  • 据说 TCC 不反对 OpenFeign?这个坑松哥必须给大家填了!

明天咱们就一起来看下另一种模式,XA 模式!

其实我感觉 seata 中的四种不同的分布式事务模式,学完 AT、TCC 以及 XA 就够了,Saga 不好玩,而且长事务自身就有很多问题,也不举荐应用。

Seata 中的 XA 模式实际上是基于 MySQL 的 XA 两阶段提交倒退进去的,所以学习 XA 模式,须要小伙伴们先了解 MySQL 中的 XA 是怎么一回事,把 MySQL 中的 XA 搞清楚了,再来学习 Seata 中的 XA 模式就容易的多了。

1. 什么是 XA 标准

1.1 什么是两阶段提交

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

先来看上面一张图:

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

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

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

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

比方上面一张图:

咱们在 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 recover 能够查看处于 prepare 状态的事务,事务回滚有三个参数:第一个参数,是以 gtrid_length 为根据,从 data 字符串上截取下来的值;第二个参数,是第一个从 data 上截取下来值之后,data 残余的值,在本案例中,第一次被截取之后,就不剩了,所以第二个参数是一个空字符串;第三个参数是 formatID 的值。

回滚之后,再执行 xa recover 就看不到货色了。

2.4 小结

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

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

3. Seata 中的 XA

3.1 Seata 中的 XA 模式

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

通过下面的介绍,大家曾经晓得了 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 特色

后面小伙伴们曾经学会了 AT 和 TCC 两种不同的分布式事务模式了,当初再退出一个 XA,咱们再来把这三个放在一起比拟下。

  1. AT 和 TCC 都是通过反向弥补将数据还原的,也就是说,通过一条更新语句将数据还原;XA 因为是 MySQL 本人的性能,所以不是反向弥补,而是正儿八经的回滚(处于 prepare 状态的数据并没有 commit,未来在二阶段能够抉择 commit 或者 rollback)。
  2. AT 和 XA 模式是无侵入的分布式事务解决方案,实用于不心愿对业务进行革新的场景,简直 0 学习老本;TCC 则有肯定的代码侵入。
  3. AT 和 XA 都是一种全自动的,无论是提交呀,回滚呀(无论是真回滚还是反向弥补),都是全自动的,就是开发者基本上不须要额定做什么事件;TCC 则是一种手动的分布式事务,一阶段的 prepare、二阶段的 commit 或者 rollback,所有逻辑都是开发者本人写的。
  4. 松哥发后面文章的时候,有小伙伴提到分布式事务的一致性问题,XA 模式是分布式强一致性的解决方案,然而因为性能低而导致应用较少。

好啦,比拟完啦,那就上代码吧!

3.3 代码实际

小伙伴们只须要搞明确后面的 AT 模式后,XA 模式其实跟 AT 模式差不多!就是替换一下数据源即可!话是这么说,不过真做起来,还是有很多坑,咱们一起来看下。

为了不便大家了解,本文我就不从新搞案例了,咱们还用上篇文章那个下订单的案例来演示。

这是一个商品下单的案例,一共有五个服务,我来和大家略微解释下:

  • eureka:这是服务注册核心。
  • account:这是账户服务,能够查问 / 批改用户的账户信息(次要是账户余额)。
  • order:这是订单服务,能够下订单。
  • storage:这是一个仓储服务,能够查问 / 批改商品的库存数量。
  • bussiness:这是业务,用户下单操作将在这里实现。

这个案例讲了一个什么事呢?

当用户想要下单的时候,调用了 bussiness 中的接口,bussiness 中的接口又调用了它本人的 service,在 service 中,首先开启了全局分布式事务,而后通过 feign 调用 storage 中的接口去扣库存,而后再通过 feign 调用 order 中的接口去创立订单(order 在创立订单的时候,不仅会创立订单,还会扣除用户账户的余额),在这个过程中,如果有任何一个环节出错了(余额有余、库存有余等导致的问题),就会触发整体的事务回滚。

本案例具体架构如下图:

这个案例就是一个典型的分布式事务问题,storage、order 以及 account 中的事务分属于不同的微服务,然而咱们心愿他们同时胜利或者同时失败。

这个案例的根本架构我这里就不反复搭建了,小伙伴们能够参考上篇文章,这里咱们次要来看 XA 事务如何增加进来。

3.3.1 数据库配置

因为 XA 模式利用的是 MySQL 本身对 XA 标准的实现,所以 XA 机制实际上是不须要 undo_log 表的,小伙伴们能够把你 AT 模式中的 undo_log 表删除啦~ 如果删除后运行 Java 程序报错,那阐明你的 XA 模式应用的不纯粹!留神看松哥前面的解说哦。

接下来我就来说几个要点。

  1. 数据库驱动

这是一个坑。松哥通过重复测试,seata 中的 XA 模式和最新版的 MySQL 驱动不兼容,运行时候会有谬误,通过测试,MySQL 8.0.11 这个版本的驱动是没问题的,所以在 account、storage 以及 order 三个须要数据库调用的服务上,记得批改一下数据库驱动依赖的版本号:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    <version>8.0.11</version>
</dependency>
  1. druid 依赖

有的小伙伴们看到这里用到了阿里的 Druid 数据库连接池,就连忙退出这个依赖!殊不知,这又掉入版本兼容的坑了,spring-cloud-starter-alibaba-seata 依赖中实际上蕴含了 druid 依赖,而且版本号是没有问题的!所以小伙伴们千万别本人手动加 druid 依赖,可能会因为版本号问题掉坑。

  1. 关掉数据源代码

接下来就是敞开掉 seata 数据源代理了,account、storage 以及 order 里边都改一下,退出如下配置:

seata.enable-auto-data-source-proxy=false
  1. 配置自定义数据源

接下来就是配置自定义数据源了,account、order 以及 storage 都要配置,如下:

@Configuration
public class DataSourceConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource druidDataSource() {return new DruidDataSource();
    }

    @Bean("dataSourceProxy")
    @Primary
    public DataSource dataSource(DruidDataSource druidDataSource) {return new DataSourceProxyXA(druidDataSource);
    }


    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSourceProxy)throws Exception{SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();}

}

先配置 DruidDataSource,但这不是咱们最终目标,最终目标是配置 DataSourceProxyXA,看名字就晓得,这就会把事务切换为 XA 模式,最初,还须要基于 DataSourceProxyXA 来配置一下 MyBatis,都是惯例操作,不多说。

好啦,就这样,咱们的 seata XA 模式就配置好啦~其余的代码都和 AT 模式一样,不再赘述。

正文完
 0