使用分布式难免会遇到分布式事务的问题,因为一个功能可能使用到多个微服务,需要跨多个数据源才能解决。
Seata解决分布式事务
以下的内容非常的表面,没有任何深入的部分,仅仅记录了如何简单的使用,更多深入的内容可以查阅官网,或者网上一些文章。
没有分布式事务遇到的问题
一切概念的前提是先看效果,不然概念怎么也看不懂。
在Github Release中下载Seata
,下载GA
版本就好了。
一如既往的解压,然后运行,但是因为我电脑使用的是JDK11
,所以Seata
启动命令里面的很多JVM
参数无法使用,在不修改JAVA_HOME
的情况下去使用这些参数,就需要修改启动命令了。
以Windows
为例,seata-server.bat
中的代码修改如下:
# ...省略70行代码
if "%JAVACMD%"=="" set JAVACMD="C:\Program Files\Java\jdk1.8.0_221\bin\java"
# ...省略余下所有代码
这样就可以明确指定JDK
要使用1.8版本。
然后就可以启动Seata
了,双击启动脚本即可。
现在要做的目的,是演示出分布式事务的效果,为了尽量的简单,就用三个微服务,连接三个数据库,每个数据库中有一张表,表中只有id
和number
,例如这样:
id | number |
---|---|
1 | 100 |
三个数据库都是如此。
现在要实现的效果是,三个微服务(A,B,C),每个微服务使用一个数据库,A来调用B和C的微服务来完成自己的工作。
目标是访问A微服务,三个数据库中的number
都要减一,变成99,98...
先搭建起来基础的功能框架,已经成千上百遍了,就略过了。
最终的三个模块如下:
伪代码:
public void decrease(Long id) {
// 数据库1中的number - 1
repository.findById(id).ifPresent(x -> {
repository.save(x.setNumber(x.getNumber() - 1));
});
// 数据库2中的number - 1
service02.decrease(id);
// 数据库3中的number - 1
service03.decrease(id);
}
先测试一下没有加入分布式事务的情况:在C微服务中随手加入一个异常,比如说by zero
,然后去看看数据库,发现A和B微服务操作数据成功了,但是C并没有操作成功,这就产生了数据不一致的情况。
加入分布式事务的支持
这个案例完全模仿官方的Demo,只不过官方的Demo已经不是新版本的了,新版本改变了一些东西。
首先要启动Seata
,然后在三个微服务中都要加入下面的代码:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
</dependency>
spring:
cloud:
alibaba:
seata:
tx-service-group: default
然后需要在三个数据库中分别都要创建一张叫做undo_log
的表,SQL点击查看
还需要将Seata
目录下面的conf
中的file.conf
和registry.conf
拷贝到resources
目录下
在A微服务的Service
方法上加入@GlobalTransactional
注解。
C微服务还是by zero
异常,这个时候再去访问A
服务,发现数据库中的数据并没有发生改变,因为C微服务出异常了,所以全部数据库已经回滚了,这就已经实现了分布式事务。
原理是因为Seata
代理了原来的数据源,所以在官方的案例中会有如下代码:
package io.seata.sample.config;
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;
/**
* 数据源配置
*
* @author HelloWoodes
*/
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
/**
* 需要将 DataSourceProxy 设置为主数据源,否则事务无法回滚
*
* @param druidDataSource The DruidDataSource
* @return The default datasource
*/
@Primary
@Bean("dataSource")
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
代码的目的是手动的配置Seata
作为数据源的代理。
这个在新版中是不能直接用的,因为新版中已经自动将Seata
作为数据源的代理了,如果非要手动去写代理数据源,需要加入下面的配置关闭自动代理数据源。
seata:
client:
support:
spring:
datasource-autoproxy: false
一些术语
XID
全局的事务ID
TC - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚,就是Seata
本身。
TM - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务,就是标注了@GlobalTransactional
注解的服务A。
RM - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚,就是每个涉及到的服务,比如说上面例子中的A、B、C。
默认使用的是AT
模式。
关于一些配置
Seata
的配置文件重要的有两个:
registry.conf
和file.conf
registry.conf
:
registry
是用来注册Seata
到注册中心的config
是用来指定读取配置文件的位置,支持file
、nacos
、apollo
、zk
、consul
、etcd3
file.conf
:
service
配置对应application.yaml
中的tx-service-group
store
配置Seata
所使用的数据源,可以配置成MySQL
,支持两种模式file
、db