当前位置: 首页 > 后端技术 > Java

分布式事务,原理简单,坑坑洼洼!

时间:2023-04-01 21:13:58 Java

分布式事务,我们已经把整体内容介绍给小伙伴们:一篇了解分布式事务解决方案!真的很容易!AT模式的实现:带领小伙伴写一个分布式事务案例!AT模式在多数据源中的应用:SpringBoot多数据源是如何处理事务的?教你一招!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-PhaseCommit,简称2PC)提交呢?说白了,两阶段提交很简单。宋大哥给大家举个简单的例子来解释一下两阶段提交:比如下图:我们在Business中分别调用了Storage、Order、Account,这三个里面的操作必须同时成功或者失败同时,但是因为三个点在不同的服务中,所以我们只能让三个服务中的操作分别执行,三个服务中事务的执行是两个阶段中的第一阶段。第一阶段执行完后,不要急于提交,因为这三个服务有可能执行失败。这时候三个服务都需要将各自阶段的执行结果上报给一个事务协调器(也就是上一篇文章SeataServer中的SeataServer),事务协调器收到消息后,如果第一阶段的三个服务执行成功,则通知三个事务分别提交,如果三个服务中有任何一个执行失败,则通知三个事务分别回滚。这就是所谓的两阶段提交。总结一下:在两阶段提交中,事务分为参与者(如上图中的具体服务)和协调者(上例中的SeataServer),参与者会通知协调者成功或操作失败,然后协调器根据所有参与者的反馈信息来决定每个参与者是提交操作还是中止操作。这里的参与者可以理解为RM,协调者可以理解为TM。但是Seata中的各种分布式事务模式基本都是在两阶段提交的基础上演化而来的,所以并不完全相同,需要小伙伴们注意。1.2什么是XA规范XA规范是由X/Open组织定义的分布式事务处理(DTP,DistributedTransactionProcessing)标准。XA规范描述了全局事务管理器和本地资源管理器之间的接口。XA规范的目的是允许在同一个事务中访问多个资源(如数据库、应用服务器、消息队列等),从而使ACID属性在应用程序之间保持有效。XA规范使用两阶段提交来保证所有资源同时提交或回滚任何特定事务。XA规范是在1990年代初期提出的。目前,几乎所有的主流数据库都支持XA规范。XA事务的基础是两阶段提交协议。需要交易协调员来确保所有交易参与者都准备就绪(阶段1)。如果协调器收到所有参与者都准备就绪的消息,它会通知所有事务它们已准备好提交(阶段2)。MySQL在这个XA事务中扮演的是参与者的角色,而不是协调者(事务管理器)。MySQL的XA事务分为内部XA和外部XA。外部XA可以参与外部分布式事务,这需要应用层作为协调者的介入;内部XA事务用于同一实例下的跨引擎事务,Binlog作为协调器。比如存储引擎commit时,需要将提交的信息写入binarylog,这是一个分布式的内部XA事务,但是binarylog的参与者是MySQL本身。MySQL在XA事务中扮演参与者的角色,而不是协调者。2.MySQL中的XA接下来宋哥将通过一个简单的例子来向大家展示MySQL中的XA是如何工作的。2.1两阶段交易提交以转账操作为例,我用MySQL中的XA交易给大家演示了从一个账户转账10元:上面的交易提交是一个两阶段交易提交的案例。具体执行步骤如下:XASTART"transfer_money":表示启动一个XA交易,后面的字符串是交易的xid,是一个唯一的字符串。启动后,事务状态变为ACTIVE。更新帐户集amount=amount-10whereaccount_no='A';这意味着执行特定的SQL。XAEND"transfer_money":表示一次XA交易结束,此时交易状态变为IDLE。XAPREPARE“transfer_money”:这将交易置于PREPARE状态。XACOMMIT"transfer_money":这用于提交交易。提交后,事务的状态为COMMITED。最后一步可以通过XACOMMIT提交,也可以通过XAROLLBACK回滚。回滚后事务的状态为ROLLBACK。另外,第四步可以省略,即一个IDLE状态的XA事务可以直接提交或者回滚。我们来看下面的流程图:从这张图中我们可以看出事务可以一步提交,也可以分两阶段提交,这两种方式都是支持的。如果是两阶段提交,prepare之后,其实是在等待其他资源管理器(RM)反馈结果。2.2直接提交交易。宋哥教你如何一步提交交易:这个比较简单,没什么好说的。本节介绍另外一个XA事务相关的命令XARECOVER,如下图:XARECOVER可以列出所有处于PREPARE状态的XA事务,其他状态的事务则不会列出,如图多于。2.3事务回滚另一个事务回滚的例子:可以看到,xarecover可以查看prepare状态的事务。事务回滚有三个参数:第一个参数是根据gtrid_length,从数据字符开始的字符串中截取的值;第二个参数是数据中截取第一个值后剩余的数据值。本例中,第一次拦截后,没有剩余值,所以第二个参数为空字符串;第三个参数是formatID的值。回滚后,再执行xarecover,就什么也看不到了。2.4总结在客户端环境中,XA事务和本地(非XA)事务是相互排斥的。如果已经通过XASTART开启了一个事务,那么在XA事务提交或者回滚之前,本地事务不会被启动。.反之,如果本地事务已经用STARTTRANSACTION启动,则在事务提交或回滚之前不能使用XA语句,XA事务只有InnoDB存储引擎支持。3.Seata3.1中的XA我们先简单了解一下Seata中的XA模式,然后在3.3节中我们再看代码实践。通过上面的介绍,大家已经知道MySQL中的XA事务是怎么回事了。Seata中的XA模式实际上是在MySQL中的XA模式的基础上实现的。Seata中的XA模式是在Seata定义的分布式事务框架内,使用事务性资源(数据库、消息服务等)来支持XA协议,利用XA协议的机制来管理分支事务。我们看下图:先简单说一下执行步骤:首先,通过TM启动全局分布式事务。每个业务SQL放在不同的XA分支中。具体执行流程为XAStart->BusinessSQL->XAEnd。这个过程和我在2.1节中演示的MySQL中XA事务的过程是一致的。分支中的XA事务执行完成后,执行XAprepare,将自己执行的状态报告给TC。其他分支事务按照步骤2、3执行。当所有的分支事务都执行完毕后,TC也会收到每个分支事务上报的执行状态。如果所有状态都OK,则TC通知所有RM执行XACommit完成事务的最终提交,否则TC通知所有RM执行XARollback进行一次事务回滚。这是Seata中的XA模式!小伙伴们只要了解了2.2节中MySQL的XA模式,那么Seata中的XA模式就很容易理解了。3.2特点前面的朋友已经了解了两种不同的分布式事务模式,AT和TCC。现在添加另一个XA,让我们将这三个放在一起进行比较。AT和TCC都是通过反向补偿恢复数据,即通过update语句恢复数据;因为XA是MySQL本身的功能,不是反向补偿,而是严重的回滚(在prepare状态的数据没有commit,以后可以选择commit或者rollback在第二阶段)。AT和XA模式是非侵入式分布式事务解决方案,适用于不想修改业务的场景,学习成本几乎为零;TCC具有一定的代码入侵。AT和XA都是全自动的,无论是提交还是回滚(不管是真回滚还是反向补偿),都是全自动的,即开发者基本不需要额外做任何事情;TCC是一个手动分布式事务,一阶段准备,两阶段提交或回滚,所有逻辑由开发者自己编写。宋哥发上一篇文章的时候,有小伙伴提到了分布式事务的一致性问题。XA模式是一种分布式强一致性的解决方案,但由于性能低下,使用较少。好了,对比完毕,我们来上传代码吧!3.3代码练习小伙伴只需要了解前面的AT模式,XA模式其实和AT模式差不多!只需更换数据源!话是这么说,但是在实践中,还是有很多坑,一起来看看吧。为了方便大家理解,本文案例不再赘述。下面就用上一篇下单的案例来演示一下。这是为产品下订单的情况。总共有五项服务。我给大家解释一下:eureka:这是服务注册中心。account:这是账户服务,可以查询/修改用户的账户信息(主要是账户余额)。order:这是订单服务,可以在这里下订单。storage:这是一个仓储服务,可以查询/修改商品的库存数量。bussiness:这里是business,用户的订单会在这里完成。这个案子是关于什么的?用户下单时,调用业务中的接口,业务中的接口调用自己的服务。服务中先开启全局分布式事务,然后通过feign调用storage中的接口扣除Inventory,然后通过feign调用接口创建订单(创建订单时,订单不仅会创建订单,还要扣除用户账户的余额),在这个过程中,如果任何一个环节出现错误(余额不足,库存不足等),都会触发交易整体回滚。本案例的具体架构如下图所示:本案例是一个典型的分布式事务问题。storage、order、account中的事务属于不同的微服务,但我们希望它们同时成功或同时失败。本例的基本结构在此不再赘述。朋友们可以参考之前的文章。这里我们主要看看如何添加XA事务。3.3.1数据库配置由于XA模式使用了MySQL自己实现的XA规范,所以XA机制其实并不需要undo_log表。小伙伴们可以把你AT模式下的undo_log表删掉~如果你删了再运行Java程序报错的话,说明你的XA模式不正宗!稍后注意宋歌的解释。接下来,我将提出几个重点。数据库驱动是个坑。经过反复测试,松哥发现seata中的XA模式与最新版本的MySQL驱动不兼容,运行时会出现错误。经测试,MySQL8.0.11版本的驱动是没有问题的,所以在account,storage,order三个需要调用数据库的服务,记得修改数据库驱动的版本号:mysqlmysql-connector-javaruntime8.0.11druid依赖一些朋友看到阿里的Druid数据库这里用到了连接池,赶紧添加这个依赖吧!殊不知,这又掉进了版本兼容的坑里。spring-cloud-starter-alibaba-seata依赖其实包含了druid依赖,版本号没问题!所以小伙伴们千万不要自己手动添加druid的依赖,可能会因为版本号的原因掉坑。关闭数据源代码下一步是关闭seata数据源代理,更改account、storage、order,添加如下配置:seata.enable-auto-data-source-proxy=false配置自定义datasource接下来是配置自定义数据源,account,order,storage都要配置,如下:德鲁伊数据源();}@Bean("dataSourceProxy")@PrimarypublicDataSourcedataSource(DruidDataSourcedruidDataSource){returnnewDataSourceProxyXA(druidDataSource);}@BeanpublicSqlSessionFactorysqlSessionFactory(DataSourcedataSourceProxy)throwsException{SqlSessionFactoryBeansqlSessionFactoryBean=newSqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSourceProxy);sqlSessionFactoryBean.setTransactionFactory(newSpringManagedTransactionFactory());返回sqlSessionFactoryBean.getObject();}}先配置DruidData来源,但这不是我们的最终目标。最终目标是配置DataSourceProxyXA。看名字就知道,这会将事务切换到XA模式。最后,需要配置基于DataSourceProxyXA的MyBatis。就是常规操作,不多说了,就这样吧,我们的seataXA模式配置好了~其他代码同AT模式,这里不再赘述。