在分布式系统下,一个业务跨越多个服务或数据源,每个服务都是一个分支事务,要保证所有分支事务最终状态一致,这样的事务就是分布式事务。
Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。
官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。
只有关系型数据库的时候的解决方案: AT模式
AT模式同样是分阶段提交的事务模型,弥补了XA模型中资源锁定周期过长的缺陷。
AT模式的问题:脏写(多线程情况下的并发问题)
阶段一RM的工作:
-
注册分支事务
-
记录undo-log(数据快照)
-
执行业务sql并提交
-
报告事务状态
阶段二时RM的工作:
-
提交时,删除undo-log即可
-
回滚时根据undo-log恢复数据到更新前
Seata的安装和配置
1.安装
1) 创建数据卷
#1、创建数据卷文件夹
mkdir /data/seata-server/config
#2、添加Seata的配置文件registry.conf
cd /data/seata-server/config
2)编写registry.conf文件并上传到/data/seata-server/config
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "192.168.136.150:8848"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "192.168.136.150:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId: "seataServer.properties"
}
}
3)nacos添加配置 .properties
# 数据存储方式,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.136.150:3306/seata-server?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
4) 启动docker容器
#进入目录
cd /opt/docker-files/
#启动
docker-compose up -d
5) 存在的问题
由于seata-server依赖于nacos,有可能存在seata启动失败的问题。单独重启seata-server即可
docker start seata
2.代码集成
需要在参与事务的每个微服务中,都做如下配置
1) 引入依赖
<!--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>1.4.2</version>
</dependency>
2)配置TC地址
#配置seata
seata:
data-source-proxy-mode: AT
#seata注册到的注册中心配置
registry:
type: nacos
nacos:
server-addr: 192.168.136.150:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-server
username: #nacos
password: #nacos
#配置集群
service:
vgroup-mapping:
seata-demo: default #配置了一个局部变量
tx-service-group: seata-demo
3.设置AT模式
1) 配置AT模式
seata:
data-source-proxy-mode: AT # 默认就是AT
2) 添加注解
给发起全局事务的入口方法添加@GlobalTransactional注解: Service的实现类的方法上
4.存在的问题
当在微服务中使用全局异常处理时,会经由SpringMVC内置的AOP对代码进行增强处理,当抛出异常时,会自动的构造错误信息返回。这是Seata的全局协调器会任务,本服务正常执行。不再进行后续的处理
5.代码改造
由于异常被我们内部处理了(我们自定义的全局异常),seata不会再处理这个异常信息,导致ServiceA的事务回滚失败。那么让所有分布式事务请求的接口不被这个全局异常拦截不就可以了吗。
首先,我们看下@RestControllerAdvice这个注解
在这个注解中有一个basePackages的属性,也就是说,如果配置了这个属性,将只会拦截basePackages指定的包下面发生的异常
所以,我们只需要指定basePackages的值,再把远程调用的所有服务抽出来
(1)设置全局异常处理的Controller包
@RestControllerAdvice(
basePackages = {
"com.xx.sd.controller",
"com.xx.as.controller",
"com.xx.xx.controller",
"com.xx.as.controller",
"com.xx.a.controller",
"com.xx.xc.controller",
"com.xx.b.controller"
}
)
#这样写代表只处理对应包下的异常
(2) 独立配置创建选课记录方法
删除原来Controller中的对应方法。编写新的Controller提取到新建的feign包下。
这样统一异常处理,只会处理指定controller包下的类。不会处理feign报下的controller