SpringCloud之Seata(分布式事务)


说明:关于SpringCloud系列的文章中的代码都在码云上面
地址: https://gitee.com/zh_0209_java/springcloud-alibaba.git

简介

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

Seate术语

  • Transaction ID XID: 全局唯一事务ID

  • TC (Transaction Coordinator) - 事务协调者
    维护全局和分支事务的状态,驱动全局事务提交或回滚。

  • TM (Transaction Manager) - 事务管理器
    定义全局事务的范围:开始全局事务、提交或回滚全局事务。

  • RM (Resource Manager) - 资源管理器
    管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚.

处理过程

在这里插入图片描述

  1. TM 向 TC 申请开启一个全局事务, 全局事务创建成功并生成一个全局唯一的XID;
  2. XID 在微服务调用链路的上下文中传播;
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  4. TM 向 TC 发起针对XID 的全局提交或回滚决议;
  5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求;

下载安装

下载地址

seata-server0.9.0版本安装

  1. 解压到当前目录
    在这里插入图片描述
  2. 修改conf 目录下file.conf配置文件
    在这里插入图片描述
    先备份原始file.conf 文件
    主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
    修改file.conf 两个地方
  • 修改service模块
    在这里插入图片描述
  • 修改store模块
    默认的store模块为
## transaction log store
store {
  ## store mode: file、db
  mode = "file"

  ## file store
  file {
    dir = "sessionStore"

    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    max-branch-session-size = 16384
    # globe session size , if exceeded throws exceptions
    max-global-session-size = 512
    # file buffer size , if exceeded allocate new buffer
    file-write-buffer-cache-size = 16384
    # when recover batch read size
    session.reload.read_size = 100
    # async, sync
    flush-disk-mode = async
  }

  ## database store
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "mysql"
    password = "mysql"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}

修改为

## transaction log store
store {
  ## store mode: file、db
  mode = "db" # 这里改为db

  ## file store
  file {
    dir = "sessionStore"

    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    max-branch-session-size = 16384
    # globe session size , if exceeded throws exceptions
    max-global-session-size = 512
    # file buffer size , if exceeded allocate new buffer
    file-write-buffer-cache-size = 16384
    # when recover batch read size
    session.reload.read_size = 100
    # async, sync
    flush-disk-mode = async
  }

  ## database store
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    # 配置自己的数据库连接信息
    url = "jdbc:mysql://localhost:3306/seata"
    user = "mysql"
    password = "mysql"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}
  1. 修改nacos-config.txt文件
    在这里插入图片描述
  2. 在数据库中新建库seata,并新建表
    在这里插入图片描述
    建表的sql在解压目录下seata\seata-0.9.0\seata\conf\db_store.sql
    在这里插入图片描述
  3. 修改conf目录下register.conf 配置文件
    在这里插入图片描述
    先启动nacos, 在启动seata,

注意:0.9.0版本的seata,匹配不了mysql8,可以匹配mysql5.7

测试分布式事务

测试业务说明

创建3个微服务,一个订单服务,一个库存服务,一个账户服务

当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单的库存,在通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。

下订单 --》 扣库存 --》 减账户(余额)
创建3个业务数据库

  • seata_order: 存储订单的数据库
  • seata_storage: 存储库存的数据库
  • seata_account: 存储账户信息的数据库

需要在三个业务库中创建对应的回滚日志表
注意:每个业务库都需要创建回滚日志表

建表sql在seata的解压目录 seata-0.9.0\seata\conf\db_undo_log.sql

-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
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=1 DEFAULT CHARSET=utf8;

在这里插入图片描述

新建3个微服务

seata-order-service2001

seata-storage-service2002

seata-account-service2003

新建seata-order-service2001

  1. 修改pom文件
<dependencies>
		<!--SpringCloud alibaba Nacos-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
		</dependency>

		<!--seata依赖-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <!--需要排除seata-all-->
			<exclusions>
				<exclusion>
					<groupId>io.seata</groupId>
					<artifactId>seata-all</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<!--使用和自己下载的版本一致-->
		<dependency>
			<groupId>io.seata</groupId>
			<artifactId>seata-all</artifactId>
			<version>0.9.0</version>
		</dependency>

		<!--open feign -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

		<!--		核心依赖-->
		<dependency>
			<groupId>com.zh.springcloud</groupId>
			<artifactId>core</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
  1. 新增application.yml 配置文件
# =====  nacos 配置  =====
server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # nacos注册中心地址
    alibaba:
      seata:
        # 自定义事务组名称需要与seata-server中file.conf 配置文件中配置的一致
        tx-service-group: zh_tx_group
  datasource:
    url: jdbc:mysql://101.201.249.238:33306/seata_order?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowMutiQueries=true
    #root
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

#mybatis plus 设置
mybatis-plus:
  mapper-locations: classpath*:mapper/*Mapper.xml
  global-config:
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
      id-type: 3
      # 默认数据库表下划线命名
      table-underline: true
      # 逻辑已删除值(默认为 1)
      logic-delete-value: 1
      # 逻辑未删除值(默认为 0)
      logic-not-delete-value: 0
  configuration:
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 返回类型为Map,显示null对应的字段
    call-setters-on-nulls: true

# 关闭feign对hystrix的支持
feign:
  hystrix:
    enabled: false

  1. 新增启动类
@SpringBootApplication
@EnableDiscoveryClient  // 开启nacos客户端
@EnableFeignClients // 开启feign 客户端
@MapperScan("com.zh.springcloud.mapper") // mapper扫描路径
public class SeataOrderServiceApplication {
	public static void main(String[] args) {
		SpringApplication.run(SeataOrderServiceApplication.class,args);
	}
}
  1. 配置代理数据源
/**
 * 数据源代理
 */
@Configuration
public class DataSourceConfiguration {

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

	@Primary
	@Bean("dataSource") //6.1 创建DataSourceProxy
	public DataSourceProxy dataSourceProxy(DataSource druidDataSource){
		return new DataSourceProxy(druidDataSource);
	}

    // 因为mybatis-plus 中自动代理了SqlSessionFactoryBean,所以这里不用写 
    //	@Bean //6.2 将原有的DataSource对象替换为DataSourceProxy
//	public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{
//		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//		sqlSessionFactoryBean.setDataSource(dataSourceProxy);
//		sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
//				.getResources("classpath*:mapper/*Mapper.xml"));
//		sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
//		return sqlSessionFactoryBean.getObject();
//	}
}

注意:我这里是使用了mybatis-plus, 因为mybatis-plus自动重写了SqlSessionFactory,所以我这里不用写,如果你使用的是mybatis,就要自己从新代理。

  1. 先启动 nacos,在启动seata, 最后启动2001服务,发现报错
    在这里插入图片描述
    经排查问题发现,必须在 resources 目录下新增 file.conf 和 registry.conf配置文件
    在这里插入图片描述
    然后重新打包,再次启动发现正常启动

  2. 在业务类源头添加注解 @GlobalTransactional(name = "zh-create-order",rollbackFor = Exception.class)

    1. rollbackFor: 代表了发生什么异常会回滚
    2. name : 对当前这个事务起的名称
    3. noRollbackFor: 表示发生什么异常不回滚

经测试发现,当发生异常是,数据会回滚,来保证数据的一致性。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值