本文已参与「新人创作礼」活动,一起开启掘金创作之路。
SpringBoot Seata 分布式事务
SpringBoot + Nacos + Dubbo + Seata
1 环境准备
1 Nacos 服务搭建
以本地 windows 环境为例
nacos GitHub下载安装服务:github.com/alibaba/nac…
我用的是 2.0.3 版本
解压后修改 conf 目录下的配置文件,主要修改 application.properties 文件中数据库相关配置
对应 SQL 在 nacos-mysql.sql
bin 目录下找到 startup.cmd 脚本启动,访问 localhost:8848/nacos ,默认 nacos/nacos 登录
2 Seata 服务搭建
同样 github 上安装服务 github.com/seata/seata…
我用的是 1.4.1 版本
同样是修改 conf 目录下的配置文件,主要 registry.conf 和 file.conf
目前都采用文件配置模式,未来可以把配置放到 nacos里
registry.conf
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = ""
password = ""
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
apolloAccesskeySecret = ""
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
持久化存储方式放到 db 数据库里,这里注意 service 块,里面的 vgroup_mapping 极为重要
file.conf
service {
#transaction service group mapping
vgroup_mapping.my_test_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1:8091"
#disable seata
disableGlobalTransaction = false
}
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata?serverTimezone=GMT%2B8"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
## redis store property
redis {
host = "127.0.0.1"
port = "6379"
password = ""
database = "0"
minConn = 1
maxConn = 10
maxTotal = 100
queryLimit = 100
}
}
bin 目录下seata-server.bat 启动
3 SpringBoot + Mybatis-plus 服务搭建
后端服务采用 SpringBoot+ Mybatis-plus 框架实现
整体模块
其中 account,order,storage 分别对应账户,订单,库存模块,api 提供 rpc 的接口,web 提供对外 web 服务。
各个模块可以单独提供对外 web 服务,我这边为了清晰,仅让 web 模块提供对外服务
2 整合 dubbo ,以 nacos 作为注册中心
dubbo 最初是 阿里 维护的,后来卖给了 apache,我这边用的也是 apache 对应的 dubbo 包
<dependencies>
<!-- Dubbo Nacos registry dependency -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- Keep latest Nacos client version -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.4</version>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.8</version>
</dependency>
<!-- dubbo依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- dubbo-start依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
</dependencies>
各个模块同时作为消费者和提供者,注册到 nacos 上
以 account 模块为例,其余模块同理
application.yml
server:
port: 8090
tomcat:
uri-encoding: UTF-8
servlet:
context-path: /account
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/seata-demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&allowMultiQueries=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:sqlmap/auto/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
dubbo:
application:
name: account
consumer:
check: false
registry:
check: false
protocol: nacos
address: nacos://127.0.0.1:8848?namespace=f3972b84-6d85-4b31-b0d3-88e8079cc23a
protocol:
name: dubbo
port: 18080
主要关注 dubbo.registry.address ,nacos 指定协议, namespace 指定命名空间
3 整合 seata
直接引入 seata 对应包
<dependency> <!-- 主要想使用 seata 1.1.0 版本 -->
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
application.yml 添加配置
seata:
enabled: true
application-id: account #可自定义
tx-service-group: my_test_tx_group #事务组名可自定义,但必须与配置中心参数一致
enable-auto-data-source-proxy: true #数据源自动代理
service:
grouplist:
default: 127.0.0.1:8091
vgroup-mapping:
my_test_tx_group: default
enable-degrade: false
disable-global-transaction: false
使用也很简单,只需要在业务层方法上添加 @GlobalTransactional 注解
@GlobalTransactional(name = "my_test_tx_group")
public void purchase(String userId, String commodityCode, int orderCount) {
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
}
4 整体测试
初始化数据库
设置一个 code 为 code 的商品,库存为 10 份
INSERT INTO `seata-demo`.`storage_tbl`(`id`, `commodity_code`, `count`) VALUES (1, 'code', 10);
设置 userId 为 1 的用户的账号,金额为 1000
INSERT INTO `seata-demo`.`account_tbl`(`id`, `user_id`, `money`) VALUES (1, '1', 1000);
用户 id 为 1 的用户,消费商品编码为 code 的商品 1 份,可以正常消费
http://localhost:8094/seata/business/v1/purchase?userId=1&commodityCode=code&orderCount=1
用户 id 改为 2 ,返回异常
http://localhost:8094/seata/business/v1/purchase?userId=2&commodityCode=code&orderCount=1
查询数据库,商品库存理应 -1 的,也进行了回滚
最后附上源码地址 seata-demo