简介
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
我们项目中使用AT模式,AT模式分为两个阶段:
一阶段:
业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
1. 开启seata事务的工程中引入相关依赖
pom.xml中引入seata的jar包,1.4.2之前的版本,都不支持一个data id的方式存放所有的seata服务器配置信息,从1.4.2后支持一个配置文件的方式,所以此处排除默认引入的1.3的包,需引入1.4.2。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!-- 要与seata服务端版本一直,所以把自带的替换掉 -->
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用1.4.2版本,对配置可以使用data-id一个配置文件包含其他所有的配置信息-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
关于spring cloud alibaba生态的版本参考地址:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
2. seata server服务器搭建
服务器下载地址:https://github.com/seata/seata/releases,对下载的工程进行解压、配置。
配置的模板文件下载地址:https://github.com/seata/seata/tree/1.4.2/script。
(1)seata/conf/file.conf
此配置项为seata 服务器的存储配置,存储方式选择db,再配置数据库的连接信息,以及处理事务的全局性表(表名使用默认的就可以)。
store {
## store mode: file、db、redis
mode = "db"
## rsa decryption public key
publicKey = ""
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## datasource = "dbcp"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
## mysql 5.xx
## driverClassName = "com.mysql.jdbc.Driver"
## mysql 8.0
driverClassName = "com.mysql.cj.jdbc.Driver"
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
url = "jdbc:mysql://ip:port/umapp_appcenter?rewriteBatchedStatements=true"
## url = "jdbc:mysql://ip:port/umapp_appcenter?useUnicode=true&characterEncoding=utf-8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=Asia/Shanghai"
user = "appcenter"
password = "123456"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
注意点:
driverClassName驱动的配置需要根据mysql的版本决定:
mysql5.+使用 driverClassName = "com.mysql.jdbc.Driver"
mysql8使用 driverClassName = "com.mysql.cj.jdbc.Driver"
(2)seata/conf/registry.conf
需要配置选用的注册中心类型(nacos),注册中心的连接信息;配置中心的类型,配置中心的连接信息。
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:7500"
group = "SEATA_GROUP"
namespace = "public"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:7500"
namespace = "public"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
注意点:
①当nacos开启安全配置(在nacos的conf/application.properties中配置nacos.core.auth.enabled=true)后,对nacos的连接信息都要带上用户名、密码等信息
②在seata1.4.2后才可以使用dataId = "seataServer.properties"的方式读取配置信息
(3)script/config-center/config.txt:
此配置信息是seata事务的相关属性,在nacos中创建data id 时,粘贴到文本值的内容,即seataServer.properties的配置项,seata使用1.4.2版本,新建的data id文件类型选择properties。若是使用seata1.4.2之前的版本,以下的每个配置项在nacos中就是一个条目,需要使用script/config-center/nacos/下的nacos-config.sh(linux或者windows下装git)或者nacos-config.py(python脚本)执行上传注册,可以参考https://blog.csdn.net/ZHANGLIZENG/article/details博客
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=true
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
# server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
# store
#model改为db
store.mode=db
store.lock.mode=file
store.session.mode=file
# store.publicKey=""
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 改为上面创建的seata服务数据库
store.db.url=jdbc:mysql://ip:port/umapp_appcenter?useUnicode=true&rewriteBatchedStatements=true
# 改为自己的数据库用户名
store.db.user=appcenter
# 改为自己的数据库密码
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
# store.redis.sentinel.masterName=""
# store.redis.sentinel.sentinelHosts=""
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
# store.redis.password=""
store.redis.queryLimit=100
# log
log.exceptionRate=100
# metrics
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
# service
# 自己命名一个vgroupMapping
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=ip:port
service.enableDegrade=false
service.disableGlobalTransaction=false
# client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
注意点
①.service.vgroupMapping.my_test_tx_group=default
中的my_test_tx_group需要与bootstrap.yml中配置的seata.tx-service-group的值一致。
②.service.vgroupMapping.my_test_tx_group=default
配置的default必须要等于registry.conf中配置的cluster="default"。
③.store.mode=db配置为db的方式,则需要配置db数据库方式的连接信息
store.db.url、store.db.user、store.db.password,此数据库存储下存放的表
global_table、branch_table、lock_table,用于记录全局性的事务信息
④.store.db.driverClassName的配置
mysql5.+使用 driverClassName = "com.mysql.jdbc.Driver"
mysql8使用 driverClassName = "com.mysql.cj.jdbc.Driver"
⑤.service.default.grouplist=ip:port为访问seata服务器的地址和端口(仅注册中心为file时使用),8091是默认端口,
也可以修改启动端口,在启动项目时加上端口:
seata-server.bat -p 18091
sh seata-server.sh -p 18091
⑥seata server需要配置集群时,只需要在启动seata server服务时指定不同的端口和节点序号即可,配置file.conf和registry.conf的内容一致,
windows下启动:
seata-server.bat -p 18091 -n 1
seata-server.bat -p 8091 -n 2
linux下启动:
sh seata-server.sh -p 18091 -n 1
sh seata-server.sh -p 8091 -n 2
⑦客户端启动时,可以看是否成功注册到seata server服务器
3.创建需要的事务表
global_table:全局事务表
branch_table:分支信息表
lock_table:加锁的表
以上三个表需要创建在seata服务器操作的db上,即file.conf中配置的数据库。
--全局事务表--
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;
-- 分支表
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;
-- 锁定表
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`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;
--seata新版本加的锁表
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
PRIMARY KEY (`lock_key`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
undo_log:回滚日志表
在每个需要开启seata事务操作的数据库下都需要建立此表。
--日志文件表--
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = INNODB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
注意点:
seata1.4.2之后,需要回滚的表日期类型不能使用datetime,可以使用timestamp
4.客户端配置
需要开启seata事务的客户端,需要配置seata的注册和配置中心,使用相关注解进行事务开启。
(1)bootstrap.yml
向项目中添加配置信息,配置项的值与seata服务器的registry.conf中一致。
seata:
enabled: true
enable-auto-data-source-proxy: true #是否开启数据源自动代理,默认为true
tx-service-group: my_test_tx_group #要与配置文件中的vgroupMapping一致
registry: #registry根据seata服务端的registry配置
type: nacos #默认为file
nacos:
application: seata-server #配置自己的seata服务
server-addr: #根据自己的seata服务配置
username: nacos #根据自己的seata服务配置
password: nacos #根据自己的seata服务配置
namespace: #根据自己的seata服务配置
cluster: default # 配置自己的seata服务cluster, 默认为 default
group: SEATA_GROUP #根据自己的seata服务配置
config:
type: nacos #默认file,如果使用file不配置下面的nacos,直接配置seata.service
nacos:
server-addr: #配置自己的nacos地址
group: SEATA_GROUP #配置自己的dev
username: nacos #配置自己的username
password: nacos #配置自己的password
namespace: #配置自己的namespace
dataId: seataServer.properties #配置自己的dataId,由于搭建服务端时把客户端的配置也写在了seataServer.properties,所以这里用了和服务端一样的配置文件,实际客户端和服务端的配置文件分离出来更好
(2)spring boot 启动程序添加数据自动代理
使用注解:@EnableAutoDataSourceProxy
例子:
@EnableAutoDataSourceProxy
@EnableDiscoveryClient
@SpringBootApplication
public class UmappCloudServiceAppcenterApplication {
public static void main(String[] args) {
SpringApplication.run(UmappCloudServiceAppcenterApplication.class, args);
}
}
(3)使用注解开启事务
@ApiOperation("添加测试数据")
@PostMapping("/addUmappTestSeata")
@GlobalTransactional
@GlobalLock
public Result<UmappTest> addUmappTestSeata(String name,Integer age,String sex) {
Result<UmappTest> result = umappTestService.addUmappTest(name,age,sex);
Result temp = seataTestFeign.addSeataTest();//添加seata测试数据
//int i = 1/0;
return result;