当前位置: 代码迷 >> 综合 >> SpringCloudAlibaba-分布式事务-Seata
  详细解决方案

SpringCloudAlibaba-分布式事务-Seata

热度:48   发布时间:2023-12-12 11:33:03.0

一、概述

Seata(Simple Extensible Autonomous Transaction Architecture 简单的可扩展自治事务架构) 是 阿里巴巴开源的分布式事务中间件,致力于提供高性能,零入侵和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata 的设计思路是将一个分布式事务可以理解成一个全局事务,下面挂了若干个分支事务,而一个分支事务是一个满足 ACID 的本地事务,因此我们可以操作分布式事务像操作本地事务一样。

二、Seata相关术语

一.TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

二.TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

三.RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

四.Transcation ID(XID)

由事务协调者创建的全局唯一的事务ID

三、Seata 工作流程

一.AT 模式工作机制(默认模式,二阶段提交,DB中实现)

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    提交异步化,非常快速地完成。
    回滚通过一阶段的回滚日志进行反向补偿。

1、一阶段(拦截SQL,解析数据,保存原数据快照与更新后快照)

拦截业务SQL,根据业务SQL解析出需要修改或者提交的表的原数据,统一放入before image的快照当中。然后更新业务数据,将更新后的数据放入after image当中。此事会进行行锁,避免其他数据进行脏读。

2、二阶段

1.事务成功

因为业务SQL已经更新过了,只需要将第一阶段保存的快照数据删掉,解锁行锁即可。

2.事务回滚

事务回滚操作就比较复杂了,分为三步。

(1).校验脏读

比较after image中的数据和数据库数据,如果两份数据完全一致,则说明没有进行脏写。

(2).还原数据

将before image 中的数据你想SQL进行数据还原

(3).删除中间数据

删除before image和after image中的数据,并且解锁。

二.TCC 模式(二阶段提交,需要用户自己去实现)

TCC模式需要用户根据自己的业务实现Try,Confirm,Cancel三个操作。TCC模式侵入式比较强,AT模式无侵入,但是AT模式会有行锁,效率较差。
TCC 模型的隔离性思想就是通过业务的改造,在第一阶段结束之后,从底层数据库资源层面的加锁过渡为上层业务层面的加锁,从而释放底层数据库锁资源,放宽分布式事务锁协议,将锁的粒度降到最低,以最大限度提高业务并发性能。
在这里插入图片描述

1、try

预留资源

2、confirm

完成业务

3、concel

发生回滚,释放Try阶段预留的资源

三.Saga 模式

之后再补充

四、Seata配置文件

一.file.conf(持久化配置文件,db)

其他的我就删掉了

如果配置方式选择的是nacos,那么这个文件可以选择不修改。

## transaction log store, only used in seata-server 事务日志存储,仅用于 seata-server
store {
    ## store mode: file、db、redismode = "db" #事务日志存储模式文件、数据库、redis## rsa decryption public keypublicKey = ""## database store propertydb {
    ## 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.jdbc.Driver" #5.0版本SQL驱动driverClassName = "com.mysql.cj.jdbc.Driver" ##8.0版本SQL驱动## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param## url = "jdbc:mysql://139.155.49.250:3306/seata?rewriteBatchedStatements=true" ##5.0版本urlurl = "jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&&serverTimezone=GMT%2B8&useInformationSchema=true" ##8.0版本urluser = "root"password = "xxxxxxx"minConn = 5maxConn = 100globalTable = "global_table" ##三张表名branchTable = "branch_table"lockTable = "lock_table"queryLimit = 100maxWait = 5000}
}

二.registry.conf(注册中心配置文件,nacos)

registry {
    # file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos" ##注册中心支持的模式nacos {
    application = "seata-server" ##服务名称serverAddr = "119.91.99.85:8849"  ##注册中心IPgroup = "SEATA_GROUP" ##分组namespace = "seata" ## 命名空间cluster = "default" ##是否集群username = "nacos" ##nacos账务密码password = "nacos"}
}
## 配置中心
config {
    # file、nacos 、apollo、zk、consul、etcd3type = "nacos"#默认是去读取file.conf中的信息nacos {
    serverAddr = "119.91.99.85:8849"namespace = "seata"group = "SEATA_GROUP"username = "nacos"password = "nacos"dataId = "seataServer.properties"}# file {
    #   name = "file.conf"# }
}

三.config.txt

注册中心的配置文件,需要用脚本把这些配置文件全部上传上去。

1、下载脚本

地址

https://github.com/seata/seata/blob/develop/script/config-center/nacos/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
docase $opt inh)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
doneif [ -z ${
    host} ]; thenhost=localhost
fi
if [ -z ${
    port} ]; thenport=8848
fi
if [ -z ${
    group} ]; thengroup="SEATA_GROUP"
fi
if [ -z ${
    tenant} ]; thentenant=""
fi
if [ -z ${
    username} ]; thenusername=""
fi
if [ -z ${
    password} ]; thenpassword=""
finacosAddr=$host:$port
contentType="content-type:application/json;charset=UTF-8"echo "set nacosAddr=$nacosAddr"
echo "set group=$group"urlencode() {
    length="${#1}"i=0while [ $length -gt $i ]; dochar="${1:$i:1}"case $char in[a-zA-Z0-9.~_-]) printf $char ;;*) printf '%%%02X' "'$char" ;;esaci=`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/nullif [ -z $(cat "${tempLog}") ]; thenecho " Please check the cluster status. "exit 1fiif [ "$(cat "${
    tempLog}")" == "true" ]; thenecho "Set $1=$2 successfully "elseecho "Set $1=$2 failure "failCount=`expr $failCount + 1`fi
}count=0
for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); docount=`expr $count + 1`key=${
    line%%=*}value=${
    line#*=}addConfig "${key}" "${value}"
doneecho "========================================================================="
echo " Complete initialization parameters, total-count:$count , failure-count:$failCount "
echo "========================================================================="if [ ${
    failCount} -eq 0 ]; thenecho " Init nacos config finished, please start seata-server. "
elseecho " init nacos config fail. "
fi

2、执行脚本

sh nacos-config.sh -h ip -t namespace

执行脚本要注意,需要把下载的脚本放到配置文件的下级目录里再去执行。

官网给的配置文件里面有一些参数是空的,如果你不给那些参数填值,执行脚本会报错的,如果用不上那个参数,报错就报错吧。

在这里插入图片描述
在这里插入图片描述

3、配置文件

## Netty配置
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
## service
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
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.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
## store
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.jdbc.Driver
store.db.url=jdbc:mysql://139.155.49.250:3306/seata?useSSL=false&&serverTimezone=GMT%2B8&useInformationSchema=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
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
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
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
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

三.服务端SQL脚本(1.4.2版本)

源链接

https://github.com/seata/seata/tree/develop/script/server
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 = InnoDBDEFAULT CHARSET = utf8;-- the table to store BranchSession data
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 = InnoDBDEFAULT CHARSET = utf8;-- the table to store lock data
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 = InnoDBDEFAULT CHARSET = utf8;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 = InnoDBDEFAULT 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);

四.客户端SQL脚本(1.4.2版本)

客户端SQL脚本根据不同的模式有不同的SQL脚本

https://github.com/seata/seata/tree/develop/script/client

1、AT模式

-- for AT mode you must to init this sql for you business database. the seata server not need it.
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 = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

2、TCC模式


-- -------------------------------- The script use tcc fence  --------------------------------
CREATE TABLE IF NOT EXISTS `tcc_fence_log`
(`xid`           VARCHAR(128)  NOT NULL COMMENT 'global id',`branch_id`     BIGINT        NOT NULL COMMENT 'branch id',`action_name`   VARCHAR(64)   NOT NULL COMMENT 'action name',`status`        TINYINT       NOT NULL COMMENT 'status(tried:1;committed:2;rollbacked:3;suspended:4)',`gmt_create`    DATETIME(3)   NOT NULL COMMENT 'create time',`gmt_modified`  DATETIME(3)   NOT NULL COMMENT 'update time',PRIMARY KEY (`xid`, `branch_id`),KEY `idx_gmt_modified` (`gmt_modified`),KEY `idx_status` (`status`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;

3、SAGA模式

-- -------------------------------- The script used for sage  --------------------------------CREATE TABLE IF NOT EXISTS `seata_state_machine_def`
(`id`               VARCHAR(32)  NOT NULL COMMENT 'id',`name`             VARCHAR(128) NOT NULL COMMENT 'name',`tenant_id`        VARCHAR(32)  NOT NULL COMMENT 'tenant id',`app_name`         VARCHAR(32)  NOT NULL COMMENT 'application name',`type`             VARCHAR(20)  COMMENT 'state language type',`comment_`         VARCHAR(255) COMMENT 'comment',`ver`              VARCHAR(16)  NOT NULL COMMENT 'version',`gmt_create`       DATETIME(3)  NOT NULL COMMENT 'create time',`status`           VARCHAR(2)   NOT NULL COMMENT 'status(AC:active|IN:inactive)',`content`          TEXT COMMENT 'content',`recover_strategy` VARCHAR(16) COMMENT 'transaction recover strategy(compensate|retry)',PRIMARY KEY (`id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;CREATE TABLE IF NOT EXISTS `seata_state_machine_inst`
(`id`                  VARCHAR(128)            NOT NULL COMMENT 'id',`machine_id`          VARCHAR(32)             NOT NULL COMMENT 'state machine definition id',`tenant_id`           VARCHAR(32)             NOT NULL COMMENT 'tenant id',`parent_id`           VARCHAR(128) COMMENT 'parent id',`gmt_started`         DATETIME(3)             NOT NULL COMMENT 'start time',`business_key`        VARCHAR(48) COMMENT 'business key',`start_params`        TEXT COMMENT 'start parameters',`gmt_end`             DATETIME(3) COMMENT 'end time',`excep`               BLOB COMMENT 'exception',`end_params`          TEXT COMMENT 'end parameters',`status`              VARCHAR(2) COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',`compensation_status` VARCHAR(2) COMMENT 'compensation status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',`is_running`          TINYINT(1) COMMENT 'is running(0 no|1 yes)',`gmt_updated`         DATETIME(3) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `unikey_buz_tenant` (`business_key`, `tenant_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;CREATE TABLE IF NOT EXISTS `seata_state_inst`
(`id`                       VARCHAR(48)  NOT NULL COMMENT 'id',`machine_inst_id`          VARCHAR(128) NOT NULL COMMENT 'state machine instance id',`name`                     VARCHAR(128) NOT NULL COMMENT 'state name',`type`                     VARCHAR(20)  COMMENT 'state type',`service_name`             VARCHAR(128) COMMENT 'service name',`service_method`           VARCHAR(128) COMMENT 'method name',`service_type`             VARCHAR(16) COMMENT 'service type',`business_key`             VARCHAR(48) COMMENT 'business key',`state_id_compensated_for` VARCHAR(50) COMMENT 'state compensated for',`state_id_retried_for`     VARCHAR(50) COMMENT 'state retried for',`gmt_started`              DATETIME(3)  NOT NULL COMMENT 'start time',`is_for_update`            TINYINT(1) COMMENT 'is service for update',`input_params`             TEXT COMMENT 'input parameters',`output_params`            TEXT COMMENT 'output parameters',`status`                   VARCHAR(2)   NOT NULL COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',`excep`                    BLOB COMMENT 'exception',`gmt_updated`              DATETIME(3) COMMENT 'update time',`gmt_end`                  DATETIME(3) COMMENT 'end time',PRIMARY KEY (`id`, `machine_inst_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;

五、案例(AT模式,配置中心Nacos,注册中心Nacos)

六、案例(TCC模式,配置中心Nacos,注册中心Nacos)

  相关解决方案