一,Seata简介;
用来管理分布式事务,由阿里巴巴出品。
【1、TC (Transaction Coordinator) - 事务协调者】
用来维护事务的,包括主事务和分支事务。
【2、TM (Transaction Manager) - 事务管理器】
管理事务的,决定了事务什么时候开启,什么时候结束,提交还是回滚。
TM用来做提交和回滚。
【3、RM (Resource Manager) - 资源管理器】
监视分支事务的状态,和TC做数据交互,把分支事务状态告诉TC。
应用场景:
不适合高并发项目,中间操作时有锁,会导致效率低下
“保证事务同时成功或同时失败” RM - TC - TM TC起到中间桥梁的作用
二,安装seata
1、基于操作系统环境安装
官网网址:Releases · apache/incubator-seata · GitHub
2、拉取docker镜像
docker pull seataio/seata-server:1.3.0
3、拷贝配置文件
先启动seata,将seata-server目录下的文件拷贝到虚拟机中
#执行命令1:
docker run -d --name seata -p 8091:8091 seataio/seata-server:1.3.0
虚拟机里,创建/usr/local/docker/seata目录。
然后执行拷贝任务,拷贝seata:/seata-server 里的资源到/usr/local/docker/seata目录下。
#执行命令2:
docker cp seata:/seata-server /usr/local/docker/seata
修改以下对应配置文件
【file.conf文件】
file.conf 文件,用来告诉seata 数据存放再哪里的。
修改store.mode为db,修改db相关配置
## 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://192.168.81.31:3308/seata"
user = "root"
password = "123456"
minConn = 5
maxConn = 30
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
queryLimit = 100
}
}
【registry.conf】
registry.conf 告诉seata,我们的注册中心和配置中心用的是什么。
修改类型为nacos,修改nacos相关配置,包括registry和config的两组。在nacos中为seata创建一个属于自己的命名空间,它的配置比较多,避免和其他配置公用。
[Linux启动nacos,配置新的命名空间]
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "192.168.81.31:8848"
group = "SEATA_GROUP"
namespace = "64a747e2-c7b9-464b-b9d9-586fa185f1dc"
cluster = "default"
username = "nacos"
password = "nacos"
}
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 = "nacos"
nacos {
serverAddr = "192.168.81.31:8848"
group = "SEATA_GROUP"
namespace = "64a747e2-c7b9-464b-b9d9-586fa185f1dc"
username = "nacos"
password = "nacos"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
4、创建seata在nacos配置中心的配置文件
官方文件模板所在路径: incubator-seata/script/config-center/config.txt at develop · apache/incubator-seata · GitHub
创建一个config.txt文件,将以下内容改成自己的信息,粘贴进去保存。
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
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
#Transaction routing rules configuration, only for the client
# ---------------------------------------------------
service.vgroupMapping.my_tx_group=default
# ---------------------------------------------------
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the 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=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
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
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
# ---------------------------------------------------
store.mode=db
store.lock.mode=db
store.session.mode=db
# ---------------------------------------------------
#Used for password encryption
store.publicKey=
#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
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
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
# ---------------------------------------------------
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.81.31:3308/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
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
#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
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
#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
修改的内容
service.vgroupMapping.my_tx_group=default
store.mode=db
store.lock.mode=db
store.session.mode=db
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.43.8:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
5、创建导入config.txt到nacos的脚本
官方文件模板所在路径: incubator-seata/script/config-center/nacos/nacos-config.sh at develop · apache/incubator-seata · GitHub
创建 nacos-config.sh 文件,粘贴以下内容,没有内容更改。
#!/bin/sh
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at、
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
while getopts ":h:p:g:t:u:w:" opt
do
case $opt in
h)
host=$OPTARG
;;
p)
port=$OPTARG
;;
g)
group=$OPTARG
;;
t)
tenant=$OPTARG
;;
u)
username=$OPTARG
;;
w)
password=$OPTARG
;;
?)
echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] [-u username] [-w password] "
exit 1
;;
esac
done
if [ -z ${host} ]; then
host=localhost
fi
if [ -z ${port} ]; then
port=8848
fi
if [ -z ${group} ]; then
group="SEATA_GROUP"
fi
if [ -z ${tenant} ]; then
tenant=""
fi
if [ -z ${username} ]; then
username=""
fi
if [ -z ${password} ]; then
password=""
fi
nacosAddr=$host:$port
contentType="content-type:application/json;charset=UTF-8"
echo "set nacosAddr=$nacosAddr"
echo "set group=$group"
urlencode() {
length="${#1}"
i=0
while [ $length -gt $i ]; do
char="${1:$i:1}"
case $char in
[a-zA-Z0-9.~_-]) printf $char ;;
*) printf '%%%02X' "'$char" ;;
esac
i=`expr $i + 1`
done
}
failCount=0
tempLog=$(mktemp -u)
function addConfig() {
dataId=`urlencode $1`
content=`urlencode $2`
curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$dataId&group=$group&content=$content&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/null
if [ -z $(cat "${tempLog}") ]; then
echo " Please check the cluster status. "
exit 1
fi
if [ "$(cat "${tempLog}")" == "true" ]; then
echo "Set $1=$2 successfully "
else
echo "Set $1=$2 failure "
failCount=`expr $failCount + 1`
fi
}
count=0
COMMENT_START="#"
for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do
if [[ "$line" =~ ^"${COMMENT_START}".* ]]; then
continue
fi
count=`expr $count + 1`
key=${line%%=*}
value=${line#*=}
addConfig "${key}" "${value}"
done
echo "========================================================================="
echo " Complete initialization parameters, total-count:$count , failure-count:$failCount "
echo "========================================================================="
if [ ${failCount} -eq 0 ]; then
echo " Init nacos config finished, please start seata-server. "
else
echo " init nacos config fail. "
fi
6、导入nacos配置
将 config.txt文件添加到seata文件夹里,同时创建子目录nacos;将nacos-config.sh文件放到nacos目录里。
导入前要启动nacos并关闭防火墙,执行如下命令:为该文件分配一个执行的权限。
#命令1:为该文件分配一个执行的权限。
chmod +x nacos-config.sh
修改下面命令里的内容,包括ip和Id。
#命令2:修改ip地址和命名空间
sh nacos-config.sh -h 192.168.81.31 -p 8848 -g SEATA_GROUP -t 64a747e2-c7b9-464b-b9d9-586fa185f1dc -u nacos -w nacos
报错说含/r的错误,是因为windows下文件的换行符合Linux不同导致的,执行以下命令:
#命令3:
sed -i 's/\r//' nacos-config.sh
#然后再重新执行一遍命令2:
sh nacos-config.sh -h 192.168.81.31 -p 8848 -g SEATA_GROUP -t 6bb9bfd3-2af4-4fd8-8f75-c91482ba43f8 -u nacos -w nacos
7、复制配置文件
seata目录下原资源可以全部删掉了。 (改完配置文件执行命令后已经将改的配置激活,自己写的配置文件就没用了。这个步骤是因为docker对文件处理不方便)
将file.conf和registry.conf文件复制到/usr/local/docker/seata/config下,为再次创建seata容器做配置准备。
为config目录授权:
chmod 777 config
8、关闭并删除之前的seata容器
停掉刚才启动的seata,而且它以后也用不到了,可以删除。
9、数据库配置
启动seata需要有以下几张表的基础支持,所以在启动前,先创建如下数据库表。启动mysql数据库。
seata需要创建三张表:
全局事务---global_table
分支事务---branch_table
全局锁-----lock_table
建表语句
DROP TABLE
IF
EXISTS `global_table`;
CREATE TABLE `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 ( 64 ),
`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` )
);
DROP TABLE
IF
EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR ( 128 ) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR ( 32 ),
`resource_id` VARCHAR ( 256 ),
`lock_key` VARCHAR ( 128 ),
`branch_type` VARCHAR ( 8 ),
`status` TINYINT,
`client_id` VARCHAR ( 64 ),
`application_data` VARCHAR ( 2000 ),
`gmt_create` datetime,
`gmt_modified` datetime,
PRIMARY KEY ( `branch_id` ),
KEY `idx_xid` ( `xid` )
);
DROP TABLE
IF
EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` VARCHAR ( 128 ) NOT NULL,
`xid` VARCHAR ( 96 ),
`transaction_id` LONG,
`branch_id` LONG,
`resource_id` VARCHAR ( 256 ),
`table_name` VARCHAR ( 100 ),
`pk` VARCHAR ( 32 ),
`gmt_create` datetime,
`gmt_modified` datetime,
PRIMARY KEY ( `row_key` )
);
10、启动seata
在执行启动命令前要先配置seata数据库
docker run --name seata \
-p 8091:8091 \
-e SEATA_IP=192.168.81.31 \
-e SEATA_PORT=8091 \
-e SEATA_CONFIG_NAME=file:/root/seata-config/registry \
-v /usr/local/docker/seata/config:/root/seata-config \
-d seataio/seata-server:1.3.0
查看日志信息,能看到端口号8091 就是启动成功了。
#查看上面的启动命令日志:
docker logs -f seata
三,应用seata
1.准备项目:
还是以openfeign工程做为例子,实现两张表的添加功能。
2.准备数据库表:
#建表代码
CREATE TABLE `score` (
`id` varchar(50) NOT NULL,
`name` varchar(50) NULL,
`score` double(7, 2) NULL ,
PRIMARY KEY (`id`) USING BTREE
)
CREATE TABLE `user` (
`id` varchar(50) NOT NULL,
`name` varchar(50) NULL,
`password` varchar(50) NULL,
PRIMARY KEY (`id`) USING BTREE
)
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;
3.准备阶段,运行项目
【正常运行】
没有异常,成功运行,两张表里都有数据。
【添加异常】
添加异常,失败运行,两张表都添加数据成功了居然
【添加Spring事务】
两个工程添加事务注解,成绩添加数据成功,用户添加数据失败:Spring事务不能跨工程项目进行事务控制
===以上是分布式的情况下,事务没有统一管理。
4.应用seata
以下操作,每个工程项目里都操作一遍。
1、添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
2、各自数据库中创建UNDO_LOG表
给每个微服务的数据库都创建这张表才能应用seata
要求:具有InnoDB引擎的MySQL。
-- 注意此处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;
3、修改启动类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
4、添加配置文件
在配置文件application.yml中,添加如下配置信息:
seata:
application-id: ${spring.application.name}
enabled: true
tx-service-group: my_tx_group
registry:
type: nacos
nacos:
server-addr: 192.168.81.31:8848
namespace: c05aaf00-2701-4d2e-8710-318752481731
group: SEATA_GROUP
username: nacos
password: nacos
config:
type: nacos
nacos:
server-addr: 192.168.81.31:8848
namespace: c05aaf00-2701-4d2e-8710-318752481731
group: SEATA_GROUP
username: nacos
password: nacos
service:
vgroup-mapping:
my_tx_group: default
#设置超时时间,默认是60秒,单位是毫秒
feign:
httpclient:
connection-timeout: 600000
connection-timer-repeat: 30000
tx-service-group: my_tx_group
service: vgroup-mapping: my_tx_group: default
与nacos的配置要一致,nacos的配置是tx-service-group.my_tx_group=default
5、添加数据源配置
com.jr.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;
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
@Primary
public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
6、添加事务注解
更改之前ServletImpl类方法里的事务注解@Transactional(rollbackFor = Exception.class),改为下面注解:
@GlobalTransactional(rollbackFor = Exception.class,timeoutMills = 300000)
7、完成上述配置,运行结果
【有bug代码时】
两张表里都没有添加进数据,事务回滚。【一个错,两个一起回滚为原来的状态;没有事务,28行代码报错不影响27行代码执行结果】
【没有bug代码时】
数据添加成功
补:
如果报以下错误