分布式事务之超详细的Seata实践记录(上)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 Tair(兼容Redis),内存型 2GB
简介: 分布式事务之超详细的Seata实践记录(上)

前两周花了些时间在研究tcc分布式事务的一些相关基础上边,这周来写一篇关于seata的实践文章。


网上关于seata落地的demo其实也蛮多的,自己在结合案例和相关文章进行实际落地的过程中踩了不少坑,所以这篇文章主要记录关于落地案例中遇到的困难。


技术选型



SpringBoot + Dubbo + JdbcTemplate + MySQL + Seata + Nacos


使用场景


购买商品的时候,扣减库存并且同时插入一条订单数据。


ps:简单的模拟场景,没有做锁定库存相关的复杂操作,只是为了验证seata能够保证多库场景下的分布式事务能够生效


网络异常,图片无法展示
|


搭建seata单机部署



首先保证搭建好nacos环境和mysql环境,关于这部分的介绍本文不做过多讲解。

mysql建表


搭建seata使用的数据库:seata


然后在seata数据库中建立三张表:


CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) COLLATE utf8_bin NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) COLLATE utf8_bin DEFAULT NULL,
  `resource_id` varchar(256) COLLATE utf8_bin DEFAULT NULL,
  `lock_key` varchar(128) COLLATE utf8_bin DEFAULT NULL,
  `branch_type` varchar(8) COLLATE utf8_bin DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) COLLATE utf8_bin DEFAULT NULL,
  `application_data` varchar(2000) COLLATE utf8_bin DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
复制代码


CREATE TABLE `global_table` (
  `xid` varchar(128) COLLATE utf8_bin NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) COLLATE utf8_bin DEFAULT NULL,
  `transaction_service_group` varchar(32) COLLATE utf8_bin DEFAULT NULL,
  `transaction_name` varchar(128) COLLATE utf8_bin DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) COLLATE utf8_bin DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
复制代码


CREATE TABLE `lock_table` (
  `row_key` varchar(128) COLLATE utf8_bin NOT NULL,
  `xid` varchar(96) COLLATE utf8_bin DEFAULT NULL,
  `transaction_id` mediumtext COLLATE utf8_bin,
  `branch_id` mediumtext COLLATE utf8_bin,
  `resource_id` varchar(256) COLLATE utf8_bin DEFAULT NULL,
  `table_name` varchar(32) COLLATE utf8_bin DEFAULT NULL,
  `pk` varchar(36) COLLATE utf8_bin DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
复制代码


网络异常,图片无法展示
|


然后是分别搭建订单数据库和商品数据库mall_ordermall_goods

mall_order数据库中创建一张简单的订单表


CREATE TABLE `t_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_no` varchar(60) COLLATE utf8_bin DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `goods_id` int(11) DEFAULT NULL,
  `stock` int(6) DEFAULT NULL COMMENT '库存',
  `unit` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '单位',
  `status` tinyint(3) DEFAULT NULL COMMENT '订单状态 0 提交中,1提交成功,2提交失败',
  `valid_status` tinyint(3) DEFAULT NULL COMMENT '订单是否有效 0有效 2无效',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单表';
复制代码


mall_order库中建立一张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=9 DEFAULT CHARSET=utf8;
复制代码


mall_goods数据库中创建一张简单的商品表


CREATE TABLE `t_goods` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `goods_name` varchar(30) COLLATE utf8_bin DEFAULT NULL,
  `stock` int(6) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='商品信息表';
复制代码


mall_goods库中建立一张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=9 DEFAULT CHARSET=utf8;
复制代码


两张undo_log是为了seata专门使用的,和业务核心流程无太多关联,这里我们暂时不用管,下边我会介绍。


接下来是seata的安装

初次学习seata,选择的是搭建单机环境进行部署。


在官方地址下载seata:

github.com/seata/seata…


本人使用的是1.1.0版本安装包,安装环境是mac pro

github.com/seata/seata…


网络异常,图片无法展示
|


安装包结构介绍

下载之后解压会看到以下文件:


网络异常,图片无法展示
|


bin 目录底下是主要的启动文件

conf 底下是相关的配置文件

config.txt 是我后边加入的一份配置文件,这里读者门可以先忽略

lib 是存放一些依赖包的目录


配置文件调整


按照官网的指导,我进入到来conf目录底下然后打开相关的配置文件:

/conf/register.conf


registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # seata的注册中心这里选择使用nacos
  type = "nacos"
  nacos {
    #注册地址
    serverAddr = "localhost:8848"
    #默认写空
    namespace = ""
    #单机版搭建这里写default就可以了
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  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
  type = "nacos"
  #同理,这里也这么配置即可
  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
  }
  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
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}
复制代码


这份配置文件主要改动点为config模块选择使用nacos,register模块也选择使用nacos,并且均需要设置相关的ip地址等属性。


然后我们来看到一份config.txt文件,如果没有就新建该文件。


config.txt文件配置如下:


transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
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
service.vgroupMapping.my_tcc=default #默认格式为service.vgroupMapping加一个自定义的事务分组名称,这里我取名为my_tcc,然后赋值为default
service.default.grouplist=127.0.0.1:8091 #这里写nacos的配置地址
service.enableDegrade=false
service.disableGlobalTransaction=false
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.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
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.support.spring.datasource.autoproxy=true 
store.mode=db #这里默认写的是file模式,现在需要改为db模式
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=dbcp #这里默认写的是使用druid数据连接池,这里我改为使用dbcp
store.db.dbType=mysql 
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://prod.min.mall.com:3306/seata?useUnicode=true
store.db.user=youruser #数据库账号名
store.db.password=yourpassword #数据库密码
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table #默认保持不变
store.db.branchTable=branch_table #默认保持不变
store.db.queryLimit=100
store.db.lockTable=lock_table #默认保持不变
store.db.maxWait=5000
store.redis.host=127.0.0.1
store.redis.port=6379
store.redis.maxConn=10
store.redis.minConn=1
store.redis.database=0
store.redis.password=null
store.redis.queryLimit=100
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
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.log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
复制代码


ps:配置文件最底部有个无用的空行,部分网友说不删除会有异常,建议删除。


将相关的seata的config.txt配置导入到nacos中

这部分操作在网上搜了很多资料,似乎都是使用以下脚本进行同步数据,这里我把脚本的内容贴出来给到各位查阅:


#!/usr/bin/env bash
# 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"
failCount=0
tempLog=$(mktemp -u)
function addConfig() {
  curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$1&group=$group&content=$2&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++ ))
  fi
}
count=0
for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do
  (( count++ ))
  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
复制代码


注意这里需要将config.txt文件拷贝到和bin目录同级别的位置才可以:


网络异常,图片无法展示
|


这里也就应对了文章开头我提及的config.txt文件为何会出现的情况了。


执行脚本


sh nacos-config.sh localhost
复制代码


导入成功之后查看nacos界面


网络异常,图片无法展示
|


然后此时再启动seata-server

简单的启动脚本:


echo "======== prepare to start seata ======="
nohup sh ./seata-server.sh &
echo "======== now, it is starting ======="
复制代码


启动成功会看到日志中输出这么一段话:


2020-11-07 15:31:53.116 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ... 
2020-11-07 15:34:43.032 INFO [UndoLogDelete_1]io.seata.server.coordinator.DefaultCoordinator.undoLogDelete:359 -no active rm channels to delete undo log
复制代码


并且nacos中有相关的seata服务注册


网络异常,图片无法展示
|


接下来便进入了java程序编码接入的环节了。

项目完整代码案例:

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
运维 Kubernetes 调度
阿里云容器服务 ACK One 分布式云容器企业落地实践
3年前的云栖大会,我们发布分布式云容器平台ACK One,随着3年的发展,很高兴看到ACK One在混合云,分布式云领域帮助到越来越多的客户,今天给大家汇报下ACK One 3年来的发展演进,以及如何帮助客户解决分布式领域多云多集群管理的挑战。
阿里云容器服务 ACK One 分布式云容器企业落地实践
|
2月前
|
SQL NoSQL 数据库
SpringCloud基础6——分布式事务,Seata
分布式事务、ACID原则、CAP定理、Seata、Seata的四种分布式方案:XA、AT、TCC、SAGA模式
SpringCloud基础6——分布式事务,Seata
|
3月前
|
存储 分布式计算 Hadoop
【揭秘Hadoop背后的秘密!】HDFS读写流程大曝光:从理论到实践,带你深入了解Hadoop分布式文件系统!
【8月更文挑战第24天】Hadoop分布式文件系统(HDFS)是Hadoop生态系统的关键组件,专为大规模数据集提供高效率存储及访问。本文深入解析HDFS数据读写流程并附带示例代码。HDFS采用NameNode和DataNode架构,前者负责元数据管理,后者承担数据块存储任务。文章通过Java示例演示了如何利用Hadoop API实现数据的写入与读取,有助于理解HDFS的工作原理及其在大数据处理中的应用价值。
93 1
|
3月前
|
机器学习/深度学习 人工智能 负载均衡
【AI大模型】分布式训练:深入探索与实践优化
在人工智能的浩瀚宇宙中,AI大模型以其惊人的性能和广泛的应用前景,正引领着技术创新的浪潮。然而,随着模型参数的指数级增长,传统的单机训练方式已难以满足需求。分布式训练作为应对这一挑战的关键技术,正逐渐成为AI研发中的标配。
200 5
|
3月前
|
存储 Kubernetes 监控
深入浅出分布式事务:理论与实践
在数字化时代的浪潮中,分布式系统如同星辰大海般浩瀚而深邃。本文将带你航行于这片星辰大海,探索分布式事务的奥秘。我们将从事务的基本概念出发,逐步深入到分布式事务的核心机制,最后通过一个实战案例,让你亲自体验分布式事务的魅力。让我们一起揭开分布式事务的神秘面纱,领略其背后的科学与艺术。
84 1
|
3月前
|
关系型数据库 MySQL 数据库
SpringCloud2023中使用Seata解决分布式事务
对于分布式系统而言,需要保证分布式系统中的数据一致性,保证数据在子系统中始终保持一致,避免业务出现问题。分布式系统中对数据的操作要么一起成功,要么一起失败,必须是一个整体性的事务。Seata简化了这个使用过程。
89 2
|
3月前
|
UED 存储 数据管理
深度解析 Uno Platform 离线状态处理技巧:从网络检测到本地存储同步,全方位提升跨平台应用在无网环境下的用户体验与数据管理策略
【8月更文挑战第31天】处理离线状态下的用户体验是现代应用开发的关键。本文通过在线笔记应用案例,介绍如何使用 Uno Platform 优雅地应对离线状态。首先,利用 `NetworkInformation` 类检测网络状态;其次,使用 SQLite 实现离线存储;然后,在网络恢复时同步数据;最后,通过 UI 反馈提升用户体验。
91 0
|
3月前
|
机器学习/深度学习 TensorFlow 数据处理
分布式训练在TensorFlow中的全面应用指南:掌握多机多卡配置与实践技巧,让大规模数据集训练变得轻而易举,大幅提升模型训练效率与性能
【8月更文挑战第31天】本文详细介绍了如何在Tensorflow中实现多机多卡的分布式训练,涵盖环境配置、模型定义、数据处理及训练执行等关键环节。通过具体示例代码,展示了使用`MultiWorkerMirroredStrategy`进行分布式训练的过程,帮助读者更好地应对大规模数据集与复杂模型带来的挑战,提升训练效率。
80 0
|
3月前
|
消息中间件 存储 Kafka
微服务实践之分布式定时任务
微服务实践之分布式定时任务
|
3月前
|
Java Nacos Docker
"揭秘!Docker部署Seata遇上Nacos,注册成功却报错?这些坑你不得不防!一网打尽解决秘籍,让你的分布式事务稳如老狗!"
【8月更文挑战第15天】在微服务架构中,Nacos搭配Seata确保数据一致性时,Docker部署Seata后可能出现客户端连接错误,如“can not connect to services-server”。此问题多由网络配置不当、配置文件错误或版本不兼容引起。解决策略包括:调整Docker网络设置确保可达性;检查并修正`file.conf`和`registry.conf`中的Nacos地址和端口;验证Seata与Nacos版本兼容性;修改配置后重启服务;参考官方文档和最佳实践进行配置。通过这些步骤,能有效排除故障,保障服务稳定运行。
268 0

热门文章

最新文章