seata是什么
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
docker部署seata
此处使用1.5.x版本的seata
安装准备
#先安装临时容器
docker run -d -p 8091:8091 -p 7091:7091 --name seata-serve seataio/seata-server:1.5.0
#拷贝文件为真正安装做准备
docker cp seata-serve:/seata-server/resources /home/mycontainers/seata/config
首先把镜像拉下来进行部署,然后把seata文件拷贝出来,目的就是为了启动seata时挂载到宿主机方便配置。 结构目录如下:
修改配置文件
根据你的项目环境进行配置application.yml
,此处是将seata注册到nacos,其他方法可查看官方文档。
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 你的nacos主机
namespace: 你的nacos命名空间
group: SEATA_GROUP
username: nacos账号
password: nacos密码
data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 你的nacos主机
group: SEATA_GROUP
namespace: 你的nacos命名空间
cluster: default
username: nacos账号
password: nacos密码
store:
# support: file 、 db 、 redis
mode: file
# db:
# datasource: druid
# db-type: mysql
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true
# user: root
# password: 123456
# min-conn: 5
# max-conn: 100
# global-table: global_table
# branch-table: branch_table
# lock-table: lock_table
# distributed-lock-table: distributed_lock
# query-limit: 100
# max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: 此处默认
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
重新启动seata
你可以删除容器或者使用cp命令复制到容器里面进行启动。此处为了安全起见删除原本的容器,重新创建一个并且挂载。
运行docker命令,进行seata启动。
docker run -d
--name seata
-e SEATA_IP=你的宿主机局域网ip
-p 8091:8091 -p 7091:7091
--restart=always
--network mynetwork
-v /home/mycontainers/seata/config/resources:/seata-server/resources
seataio/seata-server:1.5.0
这里挂载到resources文件夹,笔者尝试过挂载到config文件夹,启动失败,但官方文档是这样的,大家注意一下挂在路径。
注意
,如果不指定-e SEATA_IP=xxxxx 的情况下,默认是使用docker的网络ip,比如172.20.0.xx
,项目启动的时候会连接不上,报找不到服务的错误,请确保你的局域网IP能准确访问。nacos访问ip如下
注册seata配置到nacos
访问github,拿到官方提供的config.txt文件 github.com/seata/seata…
然后放到挂载目录的config目录下
#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.default_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=file
store.lock.mode=file
store.session.mode=file
#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.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=username
store.db.password=password
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
运行官方提供的脚本,将配置推送到nacos配置中心,脚本链接地址 github.com/seata/seata… 又或者你可以直接复制
#!/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
使用命令
sh nacos-config.sh -h nacos主机host -p nacos端口 -g SEATA_GROUP -t nacos命名空间 -u 账号 -w 密码
请根据你的实际情况进行修改
命令运行完成后nacos出现相关配置即表明配置成功
再看一下nacos是否已经注册到nacos
seata部署完毕,可参考seata官方的文档进行调试和定制seata
spring配置seata
核心依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2021.1</version>
</dependency>
创建undo_log表
SEATA AT 模式需要 UNDO_LOG
表
-- 注意此处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;
feign模块
@Component
@FeignClient(name = "order",fallbackFactory = Fallback.class)
public interface orderFeign {
@PostMapping("/orderc/create")
public ResponData createOrder(@RequestBody OrderDto orderDto);
}
商品服务
配置
server:
port: 8102
spring:
application:
name: 服务名
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: 密码
username: 用户名
url: mysql链接
seata:
registry:
type: nacos
nacos:
application: seata-server
server-addr: 10.31.98.22:8848
group : SEATA_GROUP
namespace: 命名空间
username: nacos账号
password: nacos密码
cluster: default
config:
type: nacos
nacos:
server-addr: 10.31.98.22:8848
group: SEATA_GROUP
username: nacos账号
password: nacos密码
data-id: seataServer.properties
namespace: 命名空间
enabled: true
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
注意
此处default_tx_group: default
必须和seata配置文件里面的service.vgroupMapping.default_tx_group=default
相对应,否则启动会报异常
代码
@GlobalTransactional
public void createOrder(UserImages userImages) {
// 模拟商品扣除
userImagesService.save(userImages);
OrderDto orderDto = OrderDto.builder().goodId(1001).userId(999).build();
//远程调用
orderFeign.createOrder(orderDto);
}
此处使用imageinfo表插入一条信息代替商品的扣除,seata对insert,update,delete生效
启动服务并且留意控制台输出日志
订单服务
配置
server:
port: 8103
spring:
application:
name: 服务名
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: 密码
username: 用户名
url: mysql链接
seata:
registry:
type: nacos
nacos:
application: seata-server
server-addr: 10.31.98.22:8848
group : SEATA_GROUP
namespace: 命名空间
username: nacos账号
password: nacos密码
cluster: default
config:
type: nacos
nacos:
server-addr: 10.31.98.22:8848
group: SEATA_GROUP
username: nacos账号
password: nacos密码
data-id: seataServer.properties
namespace: 命名空间
enabled: true
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
代码
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, GoodsOrder> implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional
public int createOrder(GoodsOrder goodsOrder) {
int i = 0;
i = orderMapper.insertOne(goodsOrder.getGoodId(),goodsOrder.getOrderId(),goodsOrder.getUserId());
//出现异常
// int e = 1 / 0;
return i;
}
}
启动服务,观察控制台,看到RM和TM注册成功即可
测试
正常测试
使用foxApi进行服务访问,这里由于有gateway网关的关系,调用方式会和直接调用有一定点区别
再看看控制台
事务正常提交
测试回滚
现在测试一下订单访问抛出异常
@Transactional
public int createOrder(GoodsOrder goodsOrder) {
int i = 0;
i = orderMapper.insertOne(goodsOrder.getGoodId(),goodsOrder.getOrderId(),goodsOrder.getUserId());
int e = 1 / 0;
return i;
}
调用接口,看到订单服务的控制台报错
再看看商品服务的控制台
控制台出现了rollbacked,然后再去查看数据库里面的表,发现没有插入记录
seata入门引用就到这里了,笔者会进行调试和补充,欢迎评论区留言。