springboot2使用sharding与seata实现分布式事务
·
使用shardingsphere实现了读写分离或者某些数据库字段加密的需求,在分布式环境下处理事务问题尤为关键,shardingsphere是支持分布式事务的,各有优劣。
- ShardingSphere XA:适合短事务且对性能要求较低的场景(如非云化环境)。
- Seata:在分布式系统中表现更灵活,支持高并发但需容忍短暂数据不一致。
本文使用一个例子介绍使用seata方式。开始之前你需要做一些准备工作
- 搭建nacos、mysql环境
- 准备3个springboot微服务,用于模仿用户下单行为
- 搭建seata环境配置seata并注册在nacos上,使用seata的db方式,建立seata所必须的表结构
源码、sql地址
https://github.com/Fjh7863/sharding-jdbc-seata-demo/tree/main
seata配置文件
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
username: nacos
password: nacos
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
cluster: default
username: nacos
password: nacos
context-path:
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login
服务pom文件
依赖的版本并不是最新,如果需要更高级的版本,需要自行研究springboot、springcloud、seata兼容性问题
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>order-service</artifactId>
<version>1.0</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.9</spring-boot.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<spring-cloud.version>2020.0.6</spring-cloud.version>
<sharding-sphere.version>4.1.1</sharding-sphere.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--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>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions><!-- 去掉springboot默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <!-- 引入log4j2依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- mysql驱动,适用于 8.x版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--shardingsphere-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<!--shardingsphere 配合seata-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.order.OrderServiceApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<!-- 防止加载不到 application.properties -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.conf</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
pom文件解释
- sharding-transaction-base-seata-at 是shardingsphere 配合seata的核心依赖,SeataATShardingTransactionManager.java类,实现了TMClient、RMClient的注册以及数据源的代理,实现了事务的开启、提交、回滚控制。
- 所有服务的pom依赖均相同
服务配置文件
注意使用时一定要关闭seata的数据源自动代理问题,否则会导致seata使用出现未知问题。enable-auto-data-source-proxy:false
server:
port: 30002
# MyBatis
mybatis:
# 配置类型别名
# typeAliasesPackage: com.example.springbootdemo.mysql
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:*/mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 加载全局的配置文件
# configLocation: classpath:mybatis/mybatis-config.xml
# executor-type: batch
spring:
application:
name: storage-service
cloud:
nacos:
discovery:
# 服务分组
group: service_group
server-addr: http://localhost:8848
shardingsphere:
datasource:
names: ds0
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
main:
allow-bean-definition-overriding: true
# Seata 配置
seata:
# 是否启用数据源bean的自动代理
enable-auto-data-source-proxy: false
tx-service-group: default_tx_group #分布式事务分组 配置项一定要等于 seata的service.vgroupMapping 配置名
config:
type: nacos
nacos:
server-addr: http://localhost:8848
group: SEATA_GROUP
username: nacos
password: nacos
registry:
type: nacos
nacos:
# Nacos 服务地址
server-addr: http://localhost:8848
group: SEATA_GROUP
application: seata-server # 必须和服务器配置一样
username: nacos
password: nacos
cluster: default
service:
vgroup-mapping:
default_tx_group: default #这里的default_tx_group应与上面的tx-service-group相同,default为Seata server中的集群名称
disable-global-transaction: false
client:
rm:
# 着每个事务分支在成功执行完毕后会主动向 Seata Server 报告其成功状态
report-success-enable: true
# 重试次数
report-retry-count: 5
启用shardingsphere的seata模式
- 在每个服务resources目录下新建seata.conf文件,注意需要关注seata.conf是否被打包到jar中
client {
application.id = storage-service #微服务名称
transaction.service.group = default_tx_group #事务组名称
}
测试流程
- 启动nacos
- 启动seata并注册到nacos
- 启动微服务并注册到nacos
- 模拟用户下单,触发扣减库存、扣减金额、生成订单
微服务链接seata日志
- RM注册成功

- TM注册成功

执行结果
-
事务回滚
全局事务发起方

全局事务加入方


-
事务提交
全局事务发起方

全局事务加入方


**
注意事项
- 全局事务发起方service方法
@Transactional(rollbackFor = Exception.class)
@ShardingTransactionType(TransactionType.BASE)
@Override
public Boolean order(Order order) {
// 库存扣减
Boolean storage = storageServiceFeign.decrStorage(order.getProductId(), order.getCount());
// 账户扣减
Boolean account = accountServiceFeign.account(order.getUserId(), order.getMoney());
// 生成订单
Long order1 = orderMapper.createOrder(order);
// int i = 1 / 0;
return Boolean.TRUE;
}
- 全局事务加入方service方法
@ShardingTransactionType(TransactionType.BASE)
@Override
public Boolean account(Long userId, BigDecimal money) {
String xid = RootContext.getXID();
System.out.println("account-service Current XID: " + xid);
return accountMapper.account(userId, money);
}
存在的问题
- 如果所有服务都接入了sharding-jdbc,在事务加入方的service方法上不可以添加
@Transactional(rollbackFor = Exception.class),否则会导致全局事务失效,但是这样会导致服务自身直接调用service方法时会以无事务方式运行。(添加@ShardingTransactionType(TransactionType.BASE)也不可以发起本地事务) - 可以尝试使用其他博客中推荐的方式,将数据源手动代理
推荐几篇解析博客
https://juejin.cn/post/7291832105105342491
https://seata.apache.org/zh-cn/blog/seata-datasource-proxy
https://github.com/apache/incubator-seata-samples/tree/sample-refactor
更多推荐


所有评论(0)