Seata分布式事务的使用

1.1.1 问题分析

在昨天的实名认证代码中,审核完毕后添加 id==5的演示异常,重新使用postman进行测试, 会发现 出现异常后 本地方法因为有

@Transactional注解 对ap_user ap_user_realname的操作会回滚

而 基于Feign远程调用 article服务 wemedia服务 确没有回滚

这时我们的代码 存在分布式事务问题,传统的数据库事务无法解决

image-20210902142129788

1.1.2 seata快速回顾

Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。

官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。

Seata事务管理中有三个重要的角色:

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

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

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

整体的架构如图:

image-20210902150935063

Seata基于上述架构提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

无论哪种方案,都离不开TC,也就是事务的协调者。

Seata AT模式

image-20210902151255173

1
2
3
4
5
6
7
8
9
10
11
阶段一RM的工作:
- 注册分支事务
- 记录undo-log(数据快照)
- 执行业务sql并提交
- 报告事务状态

阶段二提交时RM的工作:
- 删除undo-log即可

阶段二回滚时RM的工作:
- 根据undo-log恢复数据到更新前

1.1.3 项目集成seata

准备seata数据库

导入资料中的seata.sql

docker部署seata TC

如果使用docker安装 registry 默认使用 file 模式 存储事务数据 :

1
docker run --name seata --restart=always -p 8091:8091 -e SEATA_IP=192.168.200.130 -e SEATA_PORT=8091 -v seata-config:/seata-server/resources -id seataio/seata-server:1.4.2

我们使用nacos作为seata的配置和注册中心,方便以后的高可用 及 统一的配置管理

seata命名空间 创建配置seataServer.properties 分组为: SEATA_GROUP

image-20211118220043624

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.200.130:3306/seata?useUnicode=true&rewriteBatchedStatements=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
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000

# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
修改seataTC配置

准备好nacos配置中心后,修改seata TC端 使用nacos作为配置中心

1
2
3
4
5
6
# 进入到挂载目录
cd /var/lib/docker/volumes/seata-config/_data
# 修改注册中心配置
vi registry.conf

清空配置:%d

配置中内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
registry {
# tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
type = "nacos"
nacos {
# seata tc 服务注册到 nacos的服务名称,可以自定义 spring.application.name
application = "seata-tc-server"
serverAddr = "192.168.200.130:8848"
group = "SEATA_GROUP"
namespace = "seata"
cluster = "SH"
username = "nacos"
password = "nacos"
}
}
config {
# 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
type = "nacos"
# 配置nacos地址等信息
nacos {
serverAddr = "192.168.200.130:8848"
namespace = "seata"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}

修改后 记得重启seata

注意检查 mysql中是否准备了seata库哦~~

微服务配置seata

配置步骤参考官网

创建日志表undo_log (已创建)

分别在leadnews_user、leadnews_article、leadnews_wemedia三个库中都创建undo_log表

创建seata共享配置

在配置中心nacos 的 dev 环境中 创建share-seata.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
# 参考tc服务自己的registry.conf中的配置
type: nacos
nacos: # tc
server-addr: ${spring.profiles.ip}:8848
namespace: "seata"
group: SEATA_GROUP
application: seata-tc-server # tc服务在nacos中的服务名称
tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
service:
vgroup-mapping: # 事务组与TC服务cluster的映射关系
seata-demo: SH

修改微服务

参与分布式事务的微服务 ( leadnews-user、leadnews-wemedia、leadnews-article),引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本较低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<!--seata starter 采用1.4.2版本-->
<version>${seata.version}</version>
</dependency>
</dependencies>

修改bootstrap.yml配置

以 leadnews-user服务为例 其它一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
application:
name: leadnews-user # 服务名称
profiles:
active: dev # 开发环境配置
ip: 192.168.200.130 # 环境ip地址
cloud:
nacos:
discovery: # 注册中心地址配置
server-addr: ${spring.profiles.ip}:8848
namespace: ${spring.profiles.active}
config: # 配置中心地址配置
server-addr: ${spring.profiles.ip}:8848
namespace: ${spring.profiles.active}
file-extension: yml # data-id 后缀
name: ${spring.application.name} # data-id名称
shared-configs: # 共享配置
- data-id: share-feign.yml # 配置文件名-Data Id
group: DEFAULT_GROUP # 默认为DEFAULT_GROUP
refresh: false # 是否动态刷新,默认为false
- data-id: share-seata.yml # 配置文件名-Data Id
group: DEFAULT_GROUP # 默认为DEFAULT_GROUP
refresh: false # 是否动态刷新,默认为fals

image-20210903113210876

管理事务加全局事务注解

在实名认证审核的方法上,加上seata提供的全局事务管理注解 @GlobalTransactional 注解, 开启全局事务

image-20210903113426931

测试分布式事务

再次通过postman测试使用认证审核接口,查看出现异常时 事务是否回滚