seats解决分布式事务问题

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解决分布式事务问题_第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解决分布式事务问题_第2张图片
事务协调器Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

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

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

seats解决分布式事务问题_第3张图片
Branch就是指的分布式事务中每个独立的本地局部事务。

一阶段

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

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

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

二阶段提交

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

seats解决分布式事务问题_第5张图片

二阶段“回滚”

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

2)TCC模式(了解)

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

Seata也针对TCC做了适配兼容,支持TCC事务方案,原理前面已经介绍过,基本思路就是使用侵入业务上的补偿及事务管理器的协调来达到全局事务的一起提交及回滚。
seats解决分布式事务问题_第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解决分布式事务问题_第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解决分布式事务问题_第9张图片

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

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

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

seats解决分布式事务问题_第12张图片

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

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

3) 正常流程

发送请求:
seats解决分布式事务问题_第15张图片
结果变成:

宿舍
在这里插入图片描述
员工
在这里插入图片描述

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

发送请求:
seats解决分布式事务问题_第16张图片
结果变成:

宿舍
在这里插入图片描述
员工

在这里插入图片描述
这时,明显出问题了,jackchen是男生,住到2号楼,而2号楼现在是女生宿舍。

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

5) seata自定义SpringBootStarter介绍

整体目录结构
seats解决分布式事务问题_第17张图片
首先要指定seata的配置方法
seats解决分布式事务问题_第18张图片
在file.conf中注意seata服务的默认端口
seats解决分布式事务问题_第19张图片
主配置类讲解

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解决分布式事务问题_第20张图片

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

导入seata启动器配置
seats解决分布式事务问题_第21张图片
seats解决分布式事务问题_第22张图片
启动类上面排除SpringBoot数据源自动配置
seats解决分布式事务问题_第23张图片
seats解决分布式事务问题_第24张图片
application.yml添加配置允许覆盖SpringBoot自带的对象
seats解决分布式事务问题_第25张图片
切记,需要在主业务方法上添加@GlobalTransactional注解

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

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

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

seats解决分布式事务问题_第27张图片
确保本地仓库中有seata-spring-boot-starter的jar包和父工程的jar包
seats解决分布式事务问题_第28张图片

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解决分布式事务问题_第29张图片
seats解决分布式事务问题_第30张图片
第三步:在启动类上排除SpringBoot内置数据源自动配置
seats解决分布式事务问题_第31张图片
第四步:在主业务方法中添加@GlobalTransactional注解
seats解决分布式事务问题_第32张图片

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

让Seata的数据源代理对象,覆盖SpringBoot默认的数据源对象
seats解决分布式事务问题_第33张图片
在这里插入图片描述

第六步:重启测试

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

Memorial Day is 540 days
I miss you
xiaokeai

你可能感兴趣的:(java开发技巧集锦,分布式,数据库,mysql,spring,java)