ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringCloud事务管理AT事务管理二

2021-07-03 11:57:52  阅读:151  来源: 互联网

标签:事务管理 事务 seata SpringCloud conf file order Seata


SpringCloud事务之AT事务

1Seata-Server-TC全局事务协调器

Seata Server - TC全局事务协调器

AT 事务的三个角色:TC(事务协调器)、TM(事务管理器)和RM(资源管理器),其中 TM 和 RM 是嵌入在业务应用中的,而 TC 则是一个独立服务。

Seata Server 就是 TC,直接从官方仓库下载启动即可,下载地址:https://github.com/seata/seata/releases

Seata Server 配置

Seata Server 的配置文件有两个:

  • seata/conf/registry.conf
  • seata/conf/file.conf

Seata Server 要向注册中心进行注册,这样,其他服务就可以通过注册中心去发现 Seata Server,与 Seata Server 进行通信。

Seata 支持多款注册中心服务:nacos 、eureka、redis、zk、consul、etcd3、sofa。

我们项目中要使用 eureka 注册中心,eureka服务的连接地址、注册的服务名,这需要在 registry.conf 文件中进行配置:

registry.conf文件

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # 这里选择 eureka 注册配置
  type = "eureka"

  nacos {
	......
  }

  # eureka的注册配置
  eureka {
    # 注册中心地址
    serviceUrl = "http://localhost:8761/eureka"
    # 注册的服务ID
    application = "seata-server"
    weight = "1"
  }
  
  redis {
	......
  }
  ......

file.conf
Seata 需要存储全局事务信息、分支事务信息、全局锁信息,这些数据存储到什么位置?

针对存储位置的配置,支持放在配置中心,或者也可以放在本地文件。Seata Server 支持的配置中心服务有:nacos 、apollo、zk、consul、etcd3。

这里我们选择最简单的,使用本地文件,这需要在 registry.conf 配置文件中来指定

file.conf 中对事务信息的存储位置进行配置,存储位置支持:file、db、redis。

这里我们选择数据库作为存储位置,这需要在 file.conf 中进行配置:

store {
  ## store mode: file、db、redis
  # 这里选择数据库存储
  mode = "db"

  ## file store property
  file {
  	......
  }

  # 数据库存储
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"

	# 数据库连接配置
    url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"
    user = "root"
    password = "root"
    minConn = 5
    maxConn = 30

	# 事务日志表表名设置
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"

    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
  	......
  }
}

收件将seata-server-1.3.0.zip文件下载下来,放到一个不含中文的文件目录中,进行解压

image-20210702212025378

2启动参数设置

进入bin目录,找到seata-server.bat文件使用文本文件打开,找到85行,

image-20210702212337345

将内存的配置(目的改小内存配置,节省内存)

%JAVACMD% %JAVA_OPTS% -server -Xmx2048m -Xms2048m -Xmn1024m -Xss512k -XX:Sur......

改为

%JAVACMD% %JAVA_OPTS% -server -Xmx256m -Xms256m -Xmn128m -Xss512k -XX:Sur......

本次项目的存储使用的数据进行存储,所以要修改conf中的file.conf中的文件中的存储内容

修改conf目录下的file.conf中的文件

image-20210702213157862

修改conf目录下registry.conf文件

image-20210702213426167

启动 Seata Server

首先启动Eureka注册中心

进入Seata Server的bin目录,双击 seata-server.bat 启动 Seata Server。

查看 Eureka 注册中心 Seata Server 的注册信息:

image-20210702213828930

order订单服务添加 Seata AT 事务

业务

订单调用库存和账户,我们先从前面的订单开始。

在订单项目中要启动全局事务,还要执行订单保存的分支事务

3添加seata依赖

order-parent 添加 seata 依赖

order-parent 的 pom.xml 文件中有一段注释掉的 seata 依赖,现在可以打开它了

order-parent 的 pom.xml 文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>order-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>order-parent</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <mybatis-plus.version>3.3.2</mybatis-plus.version>
        <druid-spring-boot-starter.version>1.1.23</druid-spring-boot-starter.version>
        <seata.version>1.3.0</seata.version>
        <spring-cloud-alibaba-seata.version>2.0.0.RELEASE</spring-cloud-alibaba-seata.version>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid-spring-boot-starter.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <version>${spring-cloud-alibaba-seata.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>${seata.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

4配置order模块

配置order中的application.yml文件,TC 事务协调器通过“事务组”的方式将多个服务组织成一个全局事务。每个服务启动时要向TC注册,加入同一个事务组。

在order模块中要加入事务组

spring:
  ......
  
  cloud:
    alibaba:
      seata:
        tx-service-group: order_tx_group

......

image-20210702215005029

需要从注册中心获得 TC 的地址,这里配置注册中心的地址。

image-20210702215911776

registry.conf文件内容

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "eureka"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    # application = "default"
    # weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    cluster = "default"
    timeout = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

file.conf中的配置

在这里我们指定 TC 的服务ID seata-server

vgroupMapping.order_tx_group = “seata-server”

order_tx_group 对应 application.yml 中注册的事务组名。

# 事务组对应使用哪个协调器
# 事务组组名=协调器服务名
vgroupMapping.order_tx_group = "seata-server"

image-20210702220003543

file.conf文件内容

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  # order_tx_group 与 yml 中的 “tx-service-group: order_tx_group” 配置一致
  # “seata-server” 与 TC 服务器的注册名一致
  # 从eureka获取seata-server的地址,再向seata-server注册自己,设置group

  # 事务组对应使用哪个协调器
  # 事务组组名=协调器服务名
  vgroupMapping.order_tx_group = "seata-server"

  #only support when registry.type=file, please don't set multiple addresses
  order_tx_group.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

文件存放处

image-20210702220430554

5创建seata数据源代理

Seata AT 事务对业务代码无侵入,全自动化处理全局事务,其功能是靠 Seata 的数据源代理工具实现的。

这里我们创建 Seata 的数据源代理,并排除 Spring 默认的数据源。

package cn.tedu.order;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
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 javax.sql.DataSource;

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

    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSource(DataSource druidDataSource){
        return new DataSourceProxy(druidDataSource);
    }
}

image-20210702220929399

主程序中排除Springboot 的默认数据源:

在主程序上添加以下注解

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

image-20210702220903880

因为seat的属于源配置url与datasource不一致,所以,要在application.yml中添加一个新的数据源连接

jdbcUrl: jdbc:mysql://localhost:3307/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8

image-20210702225019226

6启动全局事务

Seata AT 对业务无侵入,所以启动全局事务非常简单,只需要添加一个 @GlobalTransactional 注解即可。

另外我们一步一步地添加全局事务并测试,这里先把 storage 和 account 调用注掉。

进入order模块中的cn.tedu.order.servie包,找到OrderServiceImpl实现类,给create方法添加全局事务,

@GlobalTransactional

image-20210702221517950

7启动测试

按顺序启动服务:

1.Eureka

2.Seata Server

3.Easy Id Generator

4.Order

image-20210702221832129

调用保存订单,地址:

http://localhost:8083/create?userId=1&productId=1&count=10&money=100

image-20210702221940987

观察控制台,看到全局事务和订单的分支事务已经启动,并可以看到全局事务ID(XID)和分支事务ID(Branch ID):

image-20210702222202698

然后观察数据库中新添加的订单数据:

image-20210702222318693

8模拟回滚

测试出现异常,回滚的情况

在OrderServiceImpl业务代码中加一个模拟异常再试一下:

throw new RuntimeException("模拟异常");

image-20210702223052905

重启 order 项目,并调用保存订单:

http://localhost:8083/create?userId=1&productId=1&count=10&money=100

可以看到全局事务回滚的日志:

image-20210702223230412

image-20210702223402164

image-20210702223439574

测试结束以后就可以把模拟异常给注释掉

image-20210702223553279

9给storage模块添加SeataAT事务

首先给storage的application.yml文件配置事务组

  #事务组的组名
  cloud:
    alibaba:
      seata:
        tx-service-group: order_tx_group

image-20210702223850285

将order模块中resiurces文件夹中的file.conf和registry.conf文件拷贝到storage模块中的resources文件夹下。

image-20210702224121899

创建seata数据源代理

将order模块中cn.tedu.order包下的DSAutoConfiguration拷贝到storage模块下的cn.tedu.storage包下

image-20210702224401155

在主程序注解排除DataSourceAutoConfiguration 自动配置类

在主启动类上添加

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

image-20210702224552700

在application.yml配置文件中新加一个seat的数据源配置

jdbcUrl: jdbc:mysql://localhost:3307/seata_storage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8

image-20210702225319005

启动分支事务

在业务方法上添加 @Transactional 注解启动本地事务:

在storage项目下cn.tedu.storage.service包中的StorageServiceImpl实现类下的decrease方法添加@Transactional注解

package cn.tedu.storage.service;

import cn.tedu.storage.mapper.StorageMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class StorageServiceImpl implements StorageService{

    @Autowired
    private StorageMapper storageMapper;

    @Transactional
    @Override
    public void decrease(Long productId, Integer count) {
        storageMapper.decrease(productId,count);
    }
}

在order的OrderServiceImpl业务代码中把调用商品减少的库存

前面我们把调用商品库存注释掉了,现把注释打开:

image-20210702230025384

10启动storage项目

按顺序启动项目:

1.Eureka

2.Seata Server

3.Easy Id Generator

4.Storage

5.Order

image-20210702230357439

调用保存订单,地址:

http://localhost:8083/create?userId=1&productId=1&count=10&money=100

image-20210702230424914

order数据库新增一条数据

image-20210702230516323

storage数据库中数据减少

image-20210702230557035

订单会调用库存,这两个服务会分别启动一个分支事务,两个分支事务一起组成一个全局事务:

a

观察两个项目的控制台都有Seata AT事务的日志,Storage 项目控制台如下:

image-20210702230906590

order模块的控制台

image-20210702230955919

11模拟异常

在storage模块在业务代码中加一个模拟异常再试一下:

throw new RuntimeException("模拟异常");

重启 storage 项目,并调用保存订单:

image-20210703103653506

http://localhost:8083/create?userId=1&productId=1&count=10&money=100

image-20210703103801017

Order模块进行了回滚

image-20210703103918237

storage模块出现了异常

image-20210703104058750

image-20210703104201329

image-20210703104252120

可以得出结论,数据回滚了

storage模块完成测试以后,要把注释给注释去掉

image-20210703104420977

11account模块添加SeataAT事务

首先在account模块中的appplication.yml添加事务组,要在spring的配置下添加

#事务组的组名
cloud:
  alibaba:
    seata:
      tx-service-group: order_tx_group

image-20210703104736242

将order项目中resources文件夹中的file.conf,registry.conf文件复制account同样的文件夹中

image-20210703105013935

创建seata数据源代理

与订单项目和库存项目中数据源代理相同,将order模块cn.tedu.order包中的DSAutoConfiguration复制到account的cn.tedu.account中

image-20210703105720020

在主启动类中排除掉DataSourceAutoConfiguration配置,在主启动类添加以下注解

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

image-20210703105857729

由于hikari 的数据库url属性名是 jdbcUrl,不是url所以需要在配置文件中添加一个jdbcUrl的配置

jdbcUrl: jdbc:mysql://localhost:3307/seata_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8

image-20210703110118386

启动分支事务

在AccoutServiceImpl业务上添加本地事务的注解

image-20210703110314674

在order的业务类中调用扣减账户金额

前面我们把调用账户注释掉了,现把注释打开:

image-20210703111126810

12启动account项目进行测试

按顺序启动项目:

Eureka

Seata Server

Easy Id Generator

Storage

Account

Order

调用保存订单,地址:

http://localhost:8083/create?userId=1&productId=1&count=10&money=100

image-20210703111515039

订单会调用库存和账户,这三个服务会分别启动一个分支事务,三个分支事务一起组成一个全局事务:

a

观察三个项目的控制台都有Seata AT事务的日志,account 项目控制台如下:

order控制台

image-20210703111652325

Storage控制台

image-20210703111751432

Account控制台

image-20210703111828972

然后观察数据库中的订单表、库存表和账户表。

order表

image-20210703112043431

storage表

image-20210703112124295

account表

image-20210703112240031

13测试Account异常

在Account的业务类AccoutServiceImpl添加异常信息

throw new RuntimeException("模拟异常");

image-20210703112526373

重启 account 项目,并调用保存订单:

http://localhost:8083/create?userId=1&productId=1&count=10&money=100

image-20210703112736431

查看数据库表 order、storage 和 account,如果执行成功会新增订单、减少库存、扣减金额,如果执行失败则数据没有变化,被回滚了。

失败时,在 order 和 storage和account 控制台可以看到回滚日志。

order控制台

image-20210703113418432

stotage控制台

image-20210703113158990

account控制台

image-20210703113014560

查看数据库表

account表

image-20210703113654542

order表

image-20210703113736687

stotage表

image-20210703113804269

完成以后把account的异常注释去掉

image-20210703113941968

SeataAT的事务完成

标签:事务管理,事务,seata,SpringCloud,conf,file,order,Seata
来源: https://blog.csdn.net/chaochao2113/article/details/118437972

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有