一、Seata概念介绍
1、Seata 是什么?
Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。
seata下载:http://seata.io/en-us/blog/download.html
选择自己的版本下载
seat中文文档:http://seata.io/zh-cn/docs/overview/what-is-seata.html
这里我们只介绍默认的AT模式与TCC模式。
2、Seata默认的AT模式介绍
前提
基于支持本地ACID事务的关系型数据库。
Java应用,通过JDBC访问数据库。
整体机制
两阶段提交协议的演变:
一阶段:
业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
写隔离
一阶段本地事务提交前,需要确保先拿到全局锁。拿不到全局锁,不能提交本地事务。拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。以一个示例来说明:
两个全局事务tx1和tx2,分别对a表的m字段进行更新操作,m的初始值1000。
tx1先开始,开启本地事务,拿到本地锁,更新操作m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的全局锁,本地提交释放本地锁。tx2后开始,开启本地事务,拿到本地锁,更新操作m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的全局锁,tx1全局提交前,该记录的全局锁被tx1持有,tx2需要重试等待全局锁。
tx1二阶段全局提交,释放全局锁 。tx2拿到全局锁 提交本地事务。
如果tx1的二阶段全局回滚,则tx1需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果tx2仍在等待该数据的全局锁,同时持有本地锁,则tx1的分支回滚会失败。分支的回滚会一直重试,直到tx2的全局锁等锁超时,放弃全局锁并回滚本地事务释放本地锁,tx1的分支回滚最终成功。
因为整个过程全局锁在tx1结束前一直是被tx1持有的,所以不会发生脏写的问题。
读隔离
在数据库本地事务隔离级别读已提交(Read Committed)或以上的基础上,Seata(AT 模式)的默认全局隔离级别是读未提交(Read Uncommitted)。
如果应用在特定场景下,必需要求全局的读已提交 ,目前Seata的方式是通过SELECT FOR UPDATE语句的代理。
SELECT FOR UPDATE语句的执行会申请全局锁 ,如果 全局锁被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE语句的本地执行)并重试。这个过程中,查询是被block住的,直到 全局锁拿到,即读取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑,Seata目前的方案并没有对所有SELECT语句都进行代理,仅针对FOR UPDATE 的SELECT语句。
3、TCC模式
回顾总览中的描述:一个分布式的全局事务,整体是两阶段提交的模型。全局事务是由若干分支事务组成的,分支事务要满足两阶段提交的模型要求,即需要每个分支事务都具备自己的:
- 一阶段prepare行为
- 二阶段commit或rollback行为
根据两阶段行为模式的不同,我们将分支事务划分为Automatic (Branch) Transaction Mode和Manual (Branch) Transaction Mode。
AT模式基于支持本地ACID事务的关系型数据库:
- 一阶段prepare行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
- 二阶段commit行为:马上成功结束,自动异步批量清理回滚日志。
- 二阶段rollback行为:通过回滚日志,自动生成补偿操作,完成数据回滚。
相应的TCC模式,不依赖于底层数据资源的事务支持:
- 一阶段prepare行为:调用自定义的prepare逻辑。
- 二阶段commit行为:调用自定义的commit逻辑。
- 二阶段rollback行为:调用自定义的rollback逻辑。
所谓TCC模式,是指支持把自定义的分支事务纳入到全局事务的管理中。
二、seata服务端与nacos整合开发
1、配置seata的服务器端的数据库
https://github.com/seata/seata/tree/1.2.0/script
server端脚本如下,同时也支持docker、kubernatesa安装
1)全局事务会话信息由3块内容构成,全局事务-->分支事务-->全局锁,对应表global_table、branch_table、lock_table 1;建立一个数据库名字随意用来做seata服务端的库,存储全局事务的会话信息
2)拿到服务端数据库的脚本文件执行并且建立表第1步三张表
https://github.com/seata/seata/tree/1.2.0/script/server/db 上面地址可以拿到数据库脚本,选择mysql然后自己执行。
2、修改服务端启动包的文件
1)启动包: seata-->conf-->file.conf,修改store.mode="db"。
2)修改file.conf自己的db连接信息
## transaction log store, only used in seata-server
store {
## store mode: file、db
mode = "db"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/seata?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true"
user = "root"
password = "root"
minConn = 5
maxConn = 1000
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
这些信息是存在file.conf当中的也就是seata启动的时候会去读取这个配置文件;如果我们使用nacos可以把这些信息放到nacos的注册中心,从而实现动态更新。
如果你需要把这些信息放到nacos配置中心就需要修改seata-->conf-->registy.conf文件当中的注册中心和配置中心的信息,修改成为nacos;(为什么需要修改config和register呢?因为如果你的seata服务器想要去配置中心读取配置,那么一定到得把自己注册到nacos;所以registy.conf当中需要配置注册中心的地址也需要配置配置中心的地址) 这样我们如果后面启动seata就可以看到他是作为了一个nacos的客户端注册到了nacos的注册中心的;记住这点seata已经可以作为客户端注册到nacos了。
3、Seata部署指南
Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
资源目录介绍:https://github.com/seata/seata/tree/1.2.0/script
Client:存放client端sql脚本,参数配置。
config-center:各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件。
Server:server端数据库脚本及各个容器配置。
注意事项:
seata-spring-boot-starter
内置GlobalTransactionScanner自动初始化功能,若外部实现初始化,请参考SeataAutoConfiguration保证依赖加载顺序
默认开启数据源自动代理,可配置seata.enable-auto-data-source-proxy: false关闭
spring-cloud-alibaba-seata
查看版本说明 2.1.0内嵌seata-all 0.7.1,2.1.1内嵌seata-all 0.9.0,2.2.0内嵌seata-spring-boot-starter 1.0.0
2.1.0和2.1.1兼容starter解决方案:
2.1.0和2.1.1兼容starter解决方案: @SpringBootApplication注解内exclude掉spring-cloud-alibaba-seata内的com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration
3、把配置信息上传到nacos配置中心
第一步:启动nacos
第二步:访问https://github.com/seata/seata/tree/1.2.0/script/config-center到这个地址上面获取config.txt,然后把config.txt复制到idea当中去编辑,推荐使用idea编辑,因为可能有编码原因,保留自己想要的信息。
这里给出的是精简后的,你们需要自己对应修改自己的信息;主要是数据库配置信息。注意这些信息是服务器端和客户端都要使用的;由于上面我们已经把seata注册到了nacos;所以他的file.conf当中的信息可以直接从nacos读取;也就是下面我们配置的信息;换句话说如果你配置了seata作为nacos的一个客户端去读取配置那么file.conf可以不用配置了;这两步是重复的;这也是网上很多资料没有说明的;换成大白话的意思就是你如果配置了registy.conf那么file.conf当中的信息基本无效——都是从配置中心读取;甚至可以删了file.conf;你们可以自己测试;如果你不配置registy.conf,那么seata就会从file.conf当中读取配置;所以file.conf和registy.conf其实只需要配置一个。
3.1、精简后的配置如下:
#事务分组——my_test_tx_group 这值会在我们客户端对应,需要注意
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://ip::3306/seata?useUnicode=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
那么这些精简后的配置如何传到nacos呢?
https://github.com/seata/seata/tree/1.2.0/script/config-center 从上面这个地址下载nacos文件下面的nacos-config.sh文件然后执行sh 你下载后的路径/nacos-config.sh;当然如果你的nacos地址不是默认的,需要修改naocs-config.sh当中指定你的路径;也可以在sh命令后面指定;
https://github.com/seata/seata/tree/1.2.0/script/config-center这个地址里面有个README.md文件有说明。
3.2、Nacos执行shell命令配置:
bash
sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t ‘namespace’
参数说明:
-h: host, the default value is localhost
-p: port, the default value is 8848
-g: Configure grouping, the default value is 'SEATA_GROUP'
-t: Tenant information, corresponding to the namespace ID field of Nacos, the default value is ''
注意:
这里我们在Nacos配置里面创建一个namespace叫seata,ID为seata_id
3.3、cd到Nacos文件夹所在目录执行命令:
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t seata_id
如果使用的都是默认配置则可以不指定参数,因为nacos-config.sh脚本里面已经包含了默认参数。
if [[ -z ${host} ]]; then
host=localhost
fi
if [[ -z ${port} ]]; then
port=8848
fi
if [[ -z ${group} ]]; then
group="SEATA_GROUP"
fi
if [[ -z ${tenant} ]]; then
tenant=""
Fi
执行完成之后,你可以看到nacos的配置中心上面多了很多配置;注意这个时候seata服务器用的就是这些配置了。你可以修改一个错误的试试是不能启动seata服务器的。
4、启动seata服务器
讲道理可以启动成功——注意不要用jdk11(测试过有问题)
直接双击seata-server.bat就可以默认启动,默认端口为8091,如果想修改端口,也可以用配置项启动,参数如下:
Options:
--host, -h
The host to bind.
Default: 0.0.0.0
--port, -p
The port to listen.
Default: 8091
--storeMode, -m
log store mode : file、db
Default: file
--help
例如需要修改默认端口:
seata-server.bat -p 18091
sh seata-server.sh -p 18091
不过linux中默认不会后台启动,需要启动命令支持一下:
nohup sh seata-server.sh -p 18091 > catalina.out 2>&1 &
当我们看到如下日志信息代表启动成功:
:RegisterTMRequest{applicationId='uni-meta-consumer', transactionServiceGroup='my_test_tx_group'},
channel:[id: 0x8aedbc89, L:/192.168.41.241:8091 - R:/192.168.41.241:61720]
三、新建seata客户端模块
1、建立微服务项目springcloudalibaba-seata
注意这里测试我们使用版本信息如下:
Springcloud版本:Hoxton.SR3
spring-cloud-alibaba:2.2.0.RELEASE
Springboot版本:2.2.6.RELEASE
io.seata版本:1.2.0
1.1、添加pom依赖
maven引入seata-spring-boot-starter、spring-cloud-alibaba-seata这两个jar其中seata-spring-boot-starter选择你对应的seata版本比如1.2。
但是spring-cloud-alibaba-seata这个jar当中自动依赖了seata-spring-boot-starter但是版本对应不上;比如spring-cloud-alibaba-seata当中依赖的seata-spring-boot-starter可能是0.9;所以需要剔除他。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.seata/seata-spring-boot-starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
1.2、配置客户端的seata
如果你的项目已经成为了nacos的客户端,那么直接可以从nacos读取第三步当中上传到nacos的各种配置;那么如何读取呢? 首先得在客户端进行配置告诉seata客户端需要去nacos注册中心去读取seata的配置。
配置客户端的yml读取nacos上的seata的配置
打开这个地址https://github.com/seata/seata/tree/1.2.0/script/client 找到spring文件夹,找到application.yml;这个yml是通用配置;你需要精简;我给出精简后的配置如下:
server:
port: 7100
seata:
enabled: true
application-id: springcloudalibaba-seata
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true
use-jdk-proxy: true
config:
type: nacos
nacos:
namespace: 162ee4db-c19c-40df-8b90-3f6db9610bd1
serverAddr: 192.168.41.241:8848
group: SEATA_GROUP
userName: ""
password: ""
registry:
type: nacos
nacos:
application: seata-server
server-addr: 192.168.41.241:8848
namespace: 162ee4db-c19c-40df-8b90-3f6db9610bd1
userName: ""
password: ""
# The bean 'springcloudalibaba-provider-user.FeignClientSpecification' could not be registered.
# A bean with that name has already been defined and overriding is disabled.
spring:
main:
allow-bean-definition-overriding: true
bootstrap.yml配置信息:
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.41.241:8848
config: #这里一定记得配置
server-addr: 192.168.41.241:8848
application:
name: springcloudalibaba-seata
特别注意:
这个配置需要在你的每一个参与分布式事务的项目当中加上——直接写到项目的yml当中就可以了。这个配置的意思就是让我们的seata客户端直接从配置中心拉取配置;
2、分布式事务测试场景设计
当然分布式事务肯定需要是至少两个项目,所以你在两个项目当中都加上这些依赖,如果你是nacos的自然还需要加上其他jar;详见下文。
建好项目之后,我们模拟一个分布式事务的场景 比如A调用B项目,在B项目里面操作数据库加上A项目当中的AController当中的a();调用B项目当中的BController当中的b();b方法操作数据库;那么则在A项目当中的AController的a()上面加上@GlobalTransactional这个注解
@RestController
public class A{
@GlobalTransactional
@GetMapping("xxxxxx")
public string a(){
通过feign调用b
}
}
注意:@GlobalTransactional注解就是分布式事务注解。
2.1、服务消费端调用提供端/update接口
springcloudalibaba-seata模块controller层入口代码如下:
@RestController
@RefreshScope
@RequestMapping("seata")
public class IndexController {
@Autowired
ProductService productService;
@GetMapping("/update")
public int update(@RequestParam String name){
int result = productService.update(name);
return result;
}
}
2.2、业务层代码实现
特别注意:seata事务注解@GlobalTransactional 标注的方法将会被seata事务框架管理,方法调用我们通过feign的方式实现远程调用(访问springcloudalibaba-seata-provider模块的updata接口)
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
SeataFeignClient userCenterFeignClient;
@GlobalTransactional // seata事务注解
public int update(String name) {
// 使用feign的方式实现调用
int result = userCenterFeignClient.update(name);
System.out.println("ProductServiceImpl----update name==>"+name+", result==="+result);
if(name.equals("500"))
System.out.println("xxxxxxxxxxxx"+1/0);
return result;
}
}
2.3、FeignClien接口层代码见:
com.chj.feign.SeataFeignClient
//@FeignClient(name = "springcloudalibaba-provider-user")
@FeignClient(name = "springcloudalibaba-seata-provider")
//@FeignClient(name = "springcloudalibaba-provider-user", configuration = UserFeignLogConfiguration.class) //细粒度代码方式配置
//@FeignClient(name = "springcloudalibaba-provider-user", configuration = GlobalFeignConfiguration.class) // 代码方式全局配置
//@FeignClient(name = "springcloudalibaba-provider-user",
//// fallback = UserCenterFeignClientFallback.class,
// fallbackFactory = UserCenterFeignClientFallbackFactory.class
//)
public interface SeataFeignClient {
/**
* http://springcloudalibaba-seata-provider/update?name=xxx
*/
@GetMapping("provider/update")
int update(@RequestParam String name);
}
至此seata client端的第一个业务调用端的代码实现已经ok,下面我们接着编写服务提供端代码。
四、服务提供端代码实现
1、新建seata业务端模块
新建模块springcloudalibaba-seata-provider
1.1、添加pom依赖:
注意这里测试我们使用版本信息如下:
Springcloud版本:Hoxton.SR3
spring-cloud-alibaba:2.2.0.RELEASE
Springboot版本:2.2.6.RELEASE
Mybatis通用版本:tk.mybatis-2.1.5
mybatis.generator:<version>1.3.6</version> --通过maven方式MGB
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-context -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Feign默认支持了 httpclient与okhttp -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.seata/seata-spring-boot-starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 方式二 : 集成通用mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<!-- 添加spring-boot的maven插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 使用maven方式执行MBG,引入插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<configurationFile>
${basedir}/src/main/resources/generator/generatorConfig.xml
</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
1.2、添加配置信息
application.yml
server:
port: 7200
spring:
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_provider?characterEncoding=utf8&serverTimezone=UTC
username: root
password: root
main:
allow-bean-definition-overriding: true
seata:
enabled: true
application-id: springcloudalibaba-seata-provider
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true
use-jdk-proxy: true
config:
type: nacos
nacos:
namespace: 162ee4db-c19c-40df-8b90-3f6db9610bd1
serverAddr: 192.168.41.241:8848
group: SEATA_GROUP
userName: ""
password: ""
registry:
type: nacos
nacos:
application: seata-server
server-addr: 192.168.41.241:8848
namespace: 162ee4db-c19c-40df-8b90-3f6db9610bd1
userName: ""
password: ""
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
# mybatis配置mapper 可以通过注解代替 @MapperScan(basePackages = "扫描包")
bootstrap.yml
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.41.241:8848
config: #这里一定记得配置
server-addr: 192.168.41.241:8848
application:
name: springcloudalibaba-seata-provider
1.3、启动来代码
注意添加Nacos服务发现注解@EnableDiscoveryClient与mybatis扫描注解
@MapperScan(basePackages = "com.chj.dao")
@SpringBootApplication
@EnableDiscoveryClient
public class SeataProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SeataProviderApplication.class, args);
}
}
1.4、业务端代码实现
控制访问代码如下:com.chj.controller.SeataController
@Slf4j
@RestController
@RefreshScope
@RequestMapping("provider")
public class SeataController {
@Autowired
ProductService productService;
@GetMapping("/update")
public int update(@RequestParam String name){
int result = productService.update(name);
log.info("springcloudalibaba-provider-user--update--result===="+result);
return result;
}
}
注意:该接口是暴露给服务消费端(springcloudalibaba-seata)调用的
五、启动服务测试
需要在你的客户端操作的数据库当中建立undo_log表;这个表用来实现sql反向补偿也就是回滚的信息,这个表的见表语句——https://github.com/seata/seata/tree/1.2.0/script/client/at/db 注意是建立在你的微服务所对应的库中;比如你的B服务链接了X库;那么则在x库中建立这个表;如果你的A服务链接了Y库;则Y库也需要这个表
1、环境准备
首先启动Nacos服务端与seata服务端
然后分别启动seata的服务提供端与服务消费端
2、访问接口测试
2.1、正常参数结果测试
因为我们代码中设置了当参数值为500的时候,会抛出异常,然后数据库数据应该回滚。
访问:http://localhost:7100/seata/update?name=200
数据可以正常修改,查看数据库如下所示:
2.2、异常情况测试
这次我们参数值设为500,再次访问结果如下:
我们可以通过debug模式,在事务方式抛出异常前查看数据库信息:
可以看到在代码执行结束前,数据库数据已经别修改了,接着我们让代码执行结束,再次查看数据库:
发现数据有回到了200,说明我们的代码确实回滚了这个事务。
查看undo_log表信息:
其中字段rollback字段的内ring信息如下:
INSERT INTO seata_provider.undo_log ( id, branch_id, xid, context, rollback_info, log_status, log_created, log_modified ) VALUES ( 7, 2012067280, '192.168.41.241:8091:2012067277', 'serializer=jackson', 0 x7B7D, 1, '2020-05-19 16:34:21', '2020-05-19 16:34:21' );
六、Seata源码方式配置服务
1、下载源码构建环境
第一步:GitHub下载源码:这里我们选择develop版本:
下载地址:http://seata.io/en-us/blog/download.html
第二步:执行mvnw.cmd --下载构建maven环境,统一maven环境配置管理(自带)
备注:mvnw.cmd里面配置会下载maven3.6.0建立自己的maven仓库,这里我们可以自己配置指定maven地址:在seata-1.2.0\.mvn\wrapper\maven-wrapper.properties文件中可以指定maven安装包地址。
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
这里我们改为自己下载好的maven地址:
distributionUrl=file://D:/tools/maven/apache-maven-3.6.3-bin.zip
第三步:maven环境准备过程会很漫长,构建好之后我们可以找到server项目里面的启动来Server,执行里面的main方法即可启动代码。
第四步:在启动之前先要修改配置
file.conf与registry.conf
2、新建业务端模块测试
Seata之所以可以完成分布式事务是因为——它的数据源需要去代理
Seata客户端配置文件application.yml中的数据源代理配置如下所示:
seata:
enabled: true
application-id: springcloudalibaba-seata
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true # 开启数据源代理(实现分布式事务的原因)
use-jdk-proxy: true
在seata1.0之前需要代码里面配置数据源代理代码实现,但是在1.0之后,spring帮助seata代理了数据源,所以我们只需要配置enable-auto-data-source-proxy: true代码里面注入数据源的时候可以直接注入:
代码示例如下:
@Configuration
@MapperScan(basePackages = "com.chj.dao.user", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSource1Config {
@Bean(name = "test1DataSource")
@Primary
public DataSource testDataSource(DBConfig1 dbConfig1) {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(dbConfig1.getDriverClassName());
druidDataSource.setUrl(dbConfig1.getJdbcUrl());
druidDataSource.setPassword(dbConfig1.getPassword());
druidDataSource.setUsername(dbConfig1.getUsername());
return druidDataSource;
}
// 代理数据源
// @Bean
// public DataSourceProxy dataSourceProxy(DataSource dataSource){
// return new DataSourceProxy(dataSource);
// }
@Bean(name = "test1SqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/user/*.xml"));
return bean.getObject();
}
备注:
File.conf 与registry.conf其实是重复的配置,如果是使用了配置中心则file的方式就不需要了。