官网地址: Seata 是什么? | Apache Seata
1、Seata术语
用来管理分布式事务,由阿里巴巴出品。
【1、TC (Transaction Coordinator) - 事务协调者】
用来维护事务的,包括主事务和分支事务。
【2、TM (Transaction Manager) - 事务管理器】
管理事务的,决定了事务什么时候开启,什么时候结束,提交还是回滚。
【3、RM (Resource Manager) - 资源管理器】
监视分支事务的状态,和TC做数据交互,把分支事务状态告诉TC。
2、SEATA 的分布式事务解决方案
seata提供了4种方式,分别是AT、TCC、SAGA、XA
①AT----该模式是Seata中最常用的模式,使用于大多数业务场景,特别是对性能要求较高时,(通过记录数据的快照,来支持事务的回滚)
原理:事务执行中Seata会保存数据执行前的快照,提交时,如果发生异常时就会回滚
优点:实现简单,性能高,适用于读多写少的场景
缺点:对数据库要求高
②TCC---适用于业务逻辑复杂,需要严格控制使用场景
三个阶段:try(尝试)、confirm(确认)、Cancke(取消)
原理:try执行预留操作,锁定必要资源、confirm如果操作都成功,确认操作、Cancle如果某操作失败,则取消所有操作
优点:控制力强,适合需要一致性的复杂业务场景
缺点:实现复杂
③Saga---长事务处理方式
使用:适用于微服务架构中,将大事务分解成小事务,实现事务的一致性和可靠性,具有继承性,可靠性和强一致性
优点:性能高,无锁,参与者可异步,高吞吐,实现简单
缺点:没有锁,会哟脏写,时效性差
④AX----使用两个阶段提交,保证事务的一致性和可靠性
原理:所有参与的资源管理器再提交前准备好,记录事务状态
优点:一致性、可靠性、标准化
缺点:会引入额外的性能开销,导致延迟增加、有阻塞风险,影响系统的吞吐量和并发性、易发生单点故障
3、安装seata 【13分钟】
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.43.8:3306/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.43.8:8848"
group = "SEATA_GROUP"
namespace = "6bb9bfd3-2af4-4fd8-8f75-c91482ba43f8"
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.43.8:8848"
group = "SEATA_GROUP"
namespace = "6bb9bfd3-2af4-4fd8-8f75-c91482ba43f8"
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配置中心的配置文件
官方文件模板所在路径: https://github.com/seata/seata/blob/develop/script/config-center/config.txt
创建一个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.43.8:3306/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的脚本
官方文件模板所在路径: https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
创建 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配置
#循环 不需要确认 删除 某一目录以及该目录下的文件和子目录。
rm -rf seata-server
将 config.txt文件添加到seata文件夹里,同时创建子目录nacos;将nacos-config.sh文件放到nacos目录里。
导入前要启动nacos并关闭防火墙,执行如下命令:为该文件分配一个执行的权限。
#命令1:为该文件分配一个执行的权限。 chmod +x nacos-config.sh
修改下面命令里的内容,包括ip和Id。
#命令2:
sh nacos-config.sh -h 192.168.43.8 -p 8848 -g SEATA_GROUP -t 6bb9bfd3-2af4-4fd8-8f75-c91482ba43f8 -u nacos -w nacos
报错说含/r的错误,是因为windows下文件的换行符合Linux不同导致的,执行以下命令:
#命令3:
sed -i 's/\r//' nacos-config.sh
#然后再重新执行一遍命令2:
sh nacos-config.sh -h 192.168.43.8 -p 8848 -g SEATA_GROUP -t 6bb9bfd3-2af4-4fd8-8f75-c91482ba43f8 -u nacos -w nacos
7、复制配置文件
seata目录下原资源可以全部删掉了。
将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 ( 36 ),
`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.43.8 \
-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
、
4、应用seata
1.准备项目:
创建两个seata工程,分别是seata1 和 seata2,还是以openfeign工程做为例子,实现整合mybatis连接数据库两张表的添加功能。
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
)
##undo_log表的创建语句,在下面的4.2里。
3.准备阶段,运行项目
【正常运行】
没有异常,成功运行,两张表里都有数据。
【添加异常】
添加异常,失败运行,两张表都添加数据成功了居然
【添加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: 172.19.186.247:8848
namespace: c05aaf00-2701-4d2e-8710-318752481731
group: SEATA_GROUP
username: nacos
password: nacos
config:
type: nacos
nacos:
server-addr: 172.19.186.247:8848
namespace: c05aaf00-2701-4d2e-8710-318752481731
group: SEATA_GROUP
username: nacos
password: nacos
service:
vgroup-mapping:
my_tx_group: default
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代码时】
两张表里都没有添加进数据,事务回滚。
【没有bug代码时】
数据添加成功