说明:关于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交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚.
处理过程
- TM 向 TC 申请开启一个全局事务, 全局事务创建成功并生成一个全局唯一的XID;
- XID 在微服务调用链路的上下文中传播;
- RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
- TM 向 TC 发起针对XID 的全局提交或回滚决议;
- TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求;
下载安装
seata-server0.9.0版本安装
- 解压到当前目录
- 修改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
}
}
- 修改nacos-config.txt文件
- 在数据库中新建库seata,并新建表
建表的sql在解压目录下seata\seata-0.9.0\seata\conf\db_store.sql
- 修改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
- 修改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>
- 新增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
- 新增启动类
@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);
}
}
- 配置代理数据源
/**
* 数据源代理
*/
@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,就要自己从新代理。
-
先启动 nacos,在启动seata, 最后启动2001服务,发现报错
经排查问题发现,必须在 resources 目录下新增 file.conf 和 registry.conf配置文件
然后重新打包,再次启动发现正常启动 -
在业务类源头添加注解
@GlobalTransactional(name = "zh-create-order",rollbackFor = Exception.class)
- rollbackFor: 代表了发生什么异常会回滚
- name : 对当前这个事务起的名称
- noRollbackFor: 表示发生什么异常不回滚
经测试发现,当发生异常是,数据会回滚,来保证数据的一致性。