seats解决分布式事务问题

(37) 2024-06-29 07:01:01

seats解决分布式事务问题

15、分布式事务:Seata简介

分布式事务框架很多:tcc-transaction、Hmily、ByteTCC、myth、EasyTransaction、tx-lcn、seata等等框架,这里有一篇关于这些框架压测的测试报告【不包括seata】:http://springcloud.cn/view/374 ,可以了解下 。

这里我们采用seata来实现分布式事务。

2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback),和社区一起共建开源分布式事务解决方案。Fescar 的愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。

Fescar 开源后,蚂蚁金服加入 Fescar 社区参与共建,并在 Fescar 0.4.0 版本中贡献了 TCC 模式。

为了打造更中立、更开放、生态更加丰富的分布式事务开源社区,经过社区核心成员的投票,大家决定对 Fescar 进行品牌升级,并更名为 Seata,意为:Simple Extensible Autonomous TransactionArchitecture,是一套一站式分布式事务解决方案。

Seata 融合了阿里巴巴和蚂蚁金服在分布式事务技术上的积累,并沉淀了新零售、云计算和新金融等场景下丰富的实践经验。

Seata简介

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

https://seata.io/zh-cn/

seats解决分布式事务问题 (https://mushiming.com/)  第1张

解决分布式事务问题,有两个设计初衷

**对业务无侵入:**即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入 高性能:减少分布式事务解决方案所带来的性能消耗

Seata目前有三种分布式事务实现方案:AT、TCC及SAGA

  • AT模式主要关注多 DB 访问的数据一致性,当然也包括多服务下的多 DB 数据访问一致性问题2PC-改进
  • TCC 模式主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题
  • Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事
    务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都
    由业务开发实现。

16、分布式事务:Seata模式说明

1)AT模式

Seata AT模式是基于XA事务演进而来的一个分布式事务中间件,XA是一个基于数据库实现的分布式事务协议,本质上和两阶段提交一样,需要数据库支持,Mysql5.6以上版本支持XA协议,其他数据库如Oracle,DB2也实现了XA接口。

seats解决分布式事务问题 (https://mushiming.com/)  第2张
事务协调器Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

事务管理器Transaction Manager(TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

**资源管理器Resource Manager (RM):**控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

seats解决分布式事务问题 (https://mushiming.com/)  第3张
Branch就是指的分布式事务中每个独立的本地局部事务。

一阶段

在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。任何提交的业务数据的更新一定有相应的回滚日志存在

seats解决分布式事务问题 (https://mushiming.com/)  第4张
基于这样的机制,分支的本地事务便可以在全局事务的第一阶段提交,并马上释放本地事务锁定的资源;这也是Seata和XA事务的不同之处,两阶段提交往往对资源的锁定需要持续到第二阶段实际的提交或者回滚操作,而有了回滚日志之后,可以在第一阶段释放对资源的锁定,降低了锁范围,提高效率,即使第二阶段发生异常需要回滚,只需找对undolog中对应数据并反解析成sql来达到回滚目的。

同时Seata通过代理数据源将业务sql的执行解析成undolog来与业务数据的更新同时入库,达到了对业务无侵入的效果。

二阶段提交

二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

seats解决分布式事务问题 (https://mushiming.com/)  第5张

二阶段“回滚”

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
seats解决分布式事务问题 (https://mushiming.com/)  第6张

2)TCC模式(了解)

2019 年 3 月,蚂蚁金服加入分布式事务 Seata 的社区共建中,并贡献其 TCC 模式。TCC 模式通常用于非关系型数据库的分布式事务的实现,作为AT模式的补充。可以与AT模式混合使用。

Seata也针对TCC做了适配兼容,支持TCC事务方案,原理前面已经介绍过,基本思路就是使用侵入业务上的补偿及事务管理器的协调来达到全局事务的一起提交及回滚。
seats解决分布式事务问题 (https://mushiming.com/)  第7张
详细可参考 https://seata.io/zh-cn/docs/dev/mode/tcc-mode.html

02、分布式事务:安装部署Seata

1)服务端安装

第一步:下载:https://github.com/seata/seata/releases

第二步:解压 seata-server-1.3.0

第三步:运行bin下的seata-server.bat
seats解决分布式事务问题 (https://mushiming.com/)  第8张

2)涉及到分布式事务的数据库添加表

seata需要在每个资源服务器中提供一张表:

CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 

此表主要用来做分布式事务日志记录,表的位置在seata服务中:
seats解决分布式事务问题 (https://mushiming.com/)  第9张

这里,我们无需单独创建这张表,直接执行资料中提供好的两个数据库sql文件即可:
seats解决分布式事务问题 (https://mushiming.com/)  第10张
效果如下:
seats解决分布式事务问题 (https://mushiming.com/)  第11张

03、分布式事务:seata案例演示

1) 解压资料中的案例并打开

seats解决分布式事务问题 (https://mushiming.com/)  第12张

2) 分布式事务案例业务简单描述

宿舍有两个:
seats解决分布式事务问题 (https://mushiming.com/)  第13张
员工有一个:
seats解决分布式事务问题 (https://mushiming.com/)  第14张
如果宿舍楼切换了,男寝变成了女寝,女寝变成了男寝,对应员工住宿的宿舍楼号也要改变。

3) 正常流程

发送请求:
seats解决分布式事务问题 (https://mushiming.com/)  第15张
结果变成:

宿舍
seats解决分布式事务问题 (https://mushiming.com/)  第16张
员工
seats解决分布式事务问题 (https://mushiming.com/)  第17张

4) 无分布式事务时出现异常

发送请求:
seats解决分布式事务问题 (https://mushiming.com/)  第18张
结果变成:

宿舍
seats解决分布式事务问题 (https://mushiming.com/)  第19张
员工

seats解决分布式事务问题 (https://mushiming.com/)  第20张
这时,明显出问题了,jackchen是男生,住到2号楼,而2号楼现在是女生宿舍。

宿舍切换失败,但是员工更新宿舍却成功了,没有回滚,导致了这个问题。

5) seata自定义SpringBootStarter介绍

整体目录结构
seats解决分布式事务问题 (https://mushiming.com/)  第21张
首先要指定seata的配置方法
seats解决分布式事务问题 (https://mushiming.com/)  第22张
在file.conf中注意seata服务的默认端口
seats解决分布式事务问题 (https://mushiming.com/)  第23张
主配置类讲解

package com.itheima.seata.config; import com.alibaba.druid.pool.DruidDataSource; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver; import io.seata.rm.datasource.DataSourceProxy; import io.seata.spring.annotation.GlobalTransactionScanner; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.type.JdbcType; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.transaction.SpringManagedTransactionFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import javax.sql.DataSource; @Configuration public class DataSourceConfig { 
    /** * 创建原有微服务的数据源对象 * @return */ @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource() { 
    DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } /** * 把原有的微服务数据源对象放入Seate的代理数据源 * @param druidDataSource * @return */ @Primary @Bean("dataSource") public DataSourceProxy dataSource(DataSource druidDataSource) { 
    return new DataSourceProxy(druidDataSource); } /** * 因为我们项目的底层是MyBatis,又因为DataSource变化为代理数据源,所以SqlSessionFactoryBean重新构建,并设置新的代理数据源 * @param dataSourceProxy * @return * @throws Exception */ @Bean public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception { 
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); MybatisConfiguration configuration = new MybatisConfiguration(); configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class); configuration.setJdbcTypeForNull(JdbcType.NULL); sqlSessionFactoryBean.setConfiguration(configuration); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } /** * 修改每个注册到Seata的事务组名称 * 当前spring.application.name为空的时候,取seata.group.name * 当前spring.application.name不为空的时候,取spring.application.name * @param environment * @return */ @Bean public GlobalTransactionScanner globalTransactionScanner(Environment environment) { 
    //事务分组名称 String applicationName = environment.getProperty("spring.application.name"); String groupName = environment.getProperty("seata.group.name"); if (applicationName == null) { 
    return new GlobalTransactionScanner(groupName == null ? "my_test_tx_group" : groupName); } else { 
    return new GlobalTransactionScanner(applicationName, groupName == null ? "my_test_tx_group" : groupName); } } } 

其实:到此我们可以发现,seata使用起来非常简单,只需要我们使用seata提供的数据源,而且为每个RM资源服务器起个名字即可。

6) 把seata服务做成starter

seats解决分布式事务问题 (https://mushiming.com/)  第24张

7) 导入每一个RM资源中测试

导入seata启动器配置
seats解决分布式事务问题 (https://mushiming.com/)  第25张
seats解决分布式事务问题 (https://mushiming.com/)  第26张
启动类上面排除SpringBoot数据源自动配置
seats解决分布式事务问题 (https://mushiming.com/)  第27张
seats解决分布式事务问题 (https://mushiming.com/)  第28张
application.yml添加配置允许覆盖SpringBoot自带的对象
seats解决分布式事务问题 (https://mushiming.com/)  第29张
切记,需要在主业务方法上添加@GlobalTransactional注解

如图所示:
seats解决分布式事务问题 (https://mushiming.com/)  第30张
再次测试,不同服务链接不同的数据库,事务都是可以控制在一起的,满足了分布式事务特性。

04、分布式事务:乐优商城中使用Seata

1) 安装seata-spring-boot-starter及其父工程

seats解决分布式事务问题 (https://mushiming.com/)  第31张
确保本地仓库中有seata-spring-boot-starter的jar包和父工程的jar包
seats解决分布式事务问题 (https://mushiming.com/)  第32张

2) 在leyou项目使用seata分布式事务

第一步:在leyou数据库中添加seata日志表
CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 
第二步:在所有需要分布式事务的RM资源微服务中导入seata-spring-boot-starter

seats解决分布式事务问题 (https://mushiming.com/)  第33张
seats解决分布式事务问题 (https://mushiming.com/)  第34张
第三步:在启动类上排除SpringBoot内置数据源自动配置
seats解决分布式事务问题 (https://mushiming.com/)  第35张
第四步:在主业务方法中添加@GlobalTransactional注解
seats解决分布式事务问题 (https://mushiming.com/)  第36张

第五步:在所有RM资源微服务中添加配置文件

让Seata的数据源代理对象,覆盖SpringBoot默认的数据源对象
seats解决分布式事务问题 (https://mushiming.com/)  第37张
seats解决分布式事务问题 (https://mushiming.com/)  第38张

第六步:重启测试

重新测试之前下单方法,看看抛出异常时是否可以回滚数据。

Memorial Day is 540 days
I miss you
xiaokeai

THE END

发表回复