Seata实现Sprincloud(Eureka+Feign)事务
目录
说明
谨以此记录学习 Seata 时踩过的坑
项目是一个微服务的架构,需要使用到“分布式事务”,在折腾了tx-lcn、tcc-transaction等几个玩意儿后,最终把目光定格在seata上,决定用seata给基于Eureka+Feign的项目添加分布式事务管理。
资料如下:
步骤
-
下载、配置、运行 Seata Server
Seata 需要使用 Server 端的配合来实现分布式事务,因此需要先部署 Seata 的 Server 环境。 -
配置共同环境
服务的调用方以及服务提供方,都需要进行一些共同的、一致的配置。 -
配置各自的环境
其实就是给服务调用方加上全局事务(分布式事务)的注解"@GlobalTransactional",服务提供方不需要额外配置。
实战
下载、配置并运行SeataServer
下载
下载地址:
http://seata.io/zh-cn/blog/download.html
Seata 1.2.0 (2020-04-20) 目录结构如下:
bin用于运行Seata Server; lib是server用到的资源库,无需理会; conf是配置文件目录。
bat为win下脚本文件; sh为unix-like系统下的脚本。
关注file.conf与register.conf
配置
根据自己需求修改配置文件—— "file.conf" 和 "registry.conf"。文章开头已贴出服务端以及应用端(客户端)配置项的说明的地址。
file.conf
此次需要用到数据库来保存 "undo_log" 等等的数据,需要在"file.conf"修改保存模式"mode"为"db",修改"db"中数据库相关的配置:
store {
## store mode: file、db
mode = "db"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/seata_server"
user = "root"
password = "root"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
mode = "db" 表明事务信息用db存储,当mode="db"时,只有db节点下的配置生效,不用管file节点
registry.conf
同样根据自己需求修改。
seata server会把自己注册到注册中心,像其它的微服务模块一样。registry.conf就是配置seata server使用何种注册中心以及如何注册。
registry节点为服务注册中心的类型及配置,此处选择type=eureka并修改对应的eureka的配置。
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
}
#配置中心的配置,根据自己需求修改,eureka不支持充当配置中心,保留默认值
config {
# file、nacos 、apollo、zk、consul、etcd3
...
...
}
运行
根据操作系统类型选择脚本运行。
基本环境的配置
由于此处使用数据库存储事务相关的数据,所以应先建库、建表。sql如下:
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
调用方和服务方的共同配置
数据库
以上是基本的数据表。另外,每个被调用的服务以及服务的调用方都需要用到 "undo_log"表用于事务的回滚,sql如下:
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
maven依赖:
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.1.0.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.2.0</version>
</dependency>
其它的配置文件
需要在resource目录下创建 "file.conf" 和 "registry.conf" 文件。这两个文件与之前下载的seata目录里面的配置文件不是一样的,两者内容如下:
file.conf
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = true
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThread-prefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#transaction service group mapping
vgroupMapping.my_test_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
#关于grouplist,只有当registry.type=file,注册中心是file方式时,才会起作用。 https://blog.csdn.net/weixin_39800144/article/details/100726116
default.grouplist = "127.0.0.1:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}
client {
rm {
asyncCommitBufferLimit = 10000
lock {
retryInterval = 10
retryTimes = 30
retryPolicyBranchRollbackOnConflict = true
}
reportRetryCount = 5
tableMetaCheckEnable = false
reportSuccessEnable = false
}
tm {
commitRetryCount = 5
rollbackRetryCount = 5
}
undo {
dataValidation = true
logSerialization = "jackson"
logTable = "undo_log"
}
log {
exceptionRate = 100
}
}
registry.conf:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
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"
session.timeout = 6000
connect.timeout = 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、springCloudConfig
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
可根据自己需求修改,一般情况下默认值即可。
参考:
application.yml的配置
application.yml:
spring:
cloud:
alibaba:
seata:
tx-service-group: my_test_tx_group
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/xx
username: root
password: root
启动类的配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
seata 需要使用代理的 DataSource, 因此不使用 DataSourceAutoConfiguration
事务的实现
基本的配置已经实现,被调用的服务的方法(即服务提供方的方法)不需要做额外的配置,只需要在调用方(即需要通过feign调用其它服务)的方法上添加 "@GlobalTransactional" 注解,如:
@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
public void createOrder(Param param){
//通过feign调用其它服务
accountFeign.checkAccount();
productFeign.checkNum();
}
拓展
在client端的配置文件"file.conf"中,可以看到如上的service节点,红框部分是事务组的名称,官方说该名称应该和Server中的配置保持一致,然而在1.2.0版本的seata server端的配置文件中找不到该配置项...所以此处保留了sample的名称,此时可以运行成功。seata的文档真是一言难尽...
项目是使用eureka作为注册中心,而在文章开头也提到对seata配置文件registry.conf的修改,把type改为"eureka",即把seata server注册到eureka中,此时,"vgroupMapping.my_test_tx_group"的值"default",就是seata server在eureka中的Application name:
参考: