springCloud组件专题(五) --- seata

一.Seata介绍

1. seata是什么

是一款开源的分布式事务解决方案,供了 AT、TCC、SAGA 和 XA 事务模式。

2.分布式事务中的概念

2.1. 二阶段提交

二阶段提交的含义就是将事务的提交分成两个步骤,分别为:

准备阶段:事务协调者询问所有参与者是否准备好提交或者回滚事务,并等待它们的响应。(注意,等待响应一般会设置时间,利用超时进行事务回退是常见的一种方式)

提交阶段:如果所有参与者都准备好提交,事务协调者通知它们提交事务;如果有任何一个参与者未能准备好或者出现错误,事务协调者通知它们回滚事务.

二阶段提交中,从第一阶段的准备阶段,业务所涉及的数据就被锁定,并且锁定跨越整个提交流程。在高并发和涉及业务模块较多的情况下 对数据库的性能影响较大,但是它提供了解决分布式系统下数据一致性问题的思路

2.2.三阶段提交

三阶段提交是对两阶段提交的改进,通过在第一阶段引入额外的准备阶段来解决某些情况下的阻塞问题。在三阶段提交中,除了准备和提交阶段外,还有一个称为"预提交"或"预准备"阶段,用于减少参与者在第二阶段的等待时间

2.3.TC,TM,RM

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。对于我们使用seata来说,TC就是我们部署的seata服务端、

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。seata的AT,XA模式中,TM可以片面的理解成加了@GlobalTransactional的方法的代理类(这个例子为了方便大家理解,并不完全准确)。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。seata的AT,XA模式中,RM可以理解成datasource(这个例子为了方便大家理解,并不完全准确)。

3.XA,AT,TCC,SAGA概念

3.1. XA

Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式,简单来说,就是seata作为一个协调者,借助数据库本身的能力进行分布式事务管理。

具体工作机制参见官网:

Seata XA 模式 | Apache Seata

3.2. AT

AT 模式是 Seata 的一种非侵入式的分布式事务解决方案,代码层面来讲,只需要一个注解就可以实现分布式事务管理,使用AT模式时候,我们操作数据库实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。简单来讲,AT将事务分成两阶段:

一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源

二阶段:无异常,提交异步化,非常快速地完成。有异常,滚通过一阶段的回滚日志进行补偿。

具体的读写隔离的原理参考官网:

https://seata.apache.org/zh-cn/docs/dev/mode/at-mode

3.3. TCC

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,其分布式事务模型直接作用于服务层,不依赖底层数据库。

从代码角度讲就是,TCC需要我们不仅要自己写业务代码,还要写失败的补偿代码,成功的提交代码。这样带来的好处就是,不仅能支持关系型数据库,还能支持redis,三方调用等任何方式的数据层修改。

具体执行模型见官网:

Seata TCC 模式 | Apache Seata

3.4.SAGA

SAGA官方说法是支持长事务的一种模式,其实简单来理解,它是流程+TCC的结合。在使用上,我们需要先定义状态机,然后对每个步骤进行编码。

其原理详见官网:

Seata Saga 模式 | Apache Seata

二.Seata-Server 搭建

2.1. Seata-Server下载

官方下载地址:https://github.com/seata/seata/releases

下载完成之后需要解压

2.2. Seata-Server配置

2.2.1. 需要打开conf目录
2.2.2. 配置registry.conf文件

这个文件用来管理seata服务的注册,我们修改Seata的注册中心和配置中心为Nacos

** 配置文件里一堆东西都不用管,type指定了nacos,只修改nacos的就行 **

复制代码
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  
  # type  指定了nacos,这里我们只需要管nacos的配置就行  后面的config也是
  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
  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"
    aclToken = ""
  }
  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的配置就行
  # 注意,这里指定了一个配置文件,叫做 seataServer.properties
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
    dataId = "seataServer.properties"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
    aclToken = ""
  }
  apollo {
    appId = "seata-server"
    ## apolloConfigService will cover apolloMeta
    apolloMeta = "http://192.168.1.204:8801"
    apolloConfigService = "http://192.168.1.204:8080"
    namespace = "application"
    apolloAccesskeySecret = ""
    cluster = "seata"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
    nodePath = "/seata/seata.properties"
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}
2.2.3. 修改file.conf

这个文件管理Seata的存储模式。

Server端存储模式(store.mode)支持三种:

  1. file:单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)

  2. DB:高可用模式,全局事务会话信息通过DB共享,相对性能差一些

  3. redis:Seata-Server1.3及以上版本支持,性能较高,存在事务信息丢失风险,需要配合实际场景使用。

我们把Seata的默认存储模式修改为数据库"DB",同时需要配置JDBC 如下:

复制代码
## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "db"
  ## rsa decryption public key
  publicKey = ""
  ## 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.jdbc.Driver"
    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
    url = "jdbc:mysql://localhost:3306/seata?useUnicode=true&characterEncoding=UTF-8"
    user = "root"
    password = "root"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
    ## redis mode: single、sentinel
    mode = "single"
    ## single mode property
    single {
      host = "127.0.0.1"
      port = "6379"
    }
    ## sentinel mode property
    sentinel {
      masterName = ""
      ## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381"
      sentinelHosts = ""
    }
    password = ""
    database = "0"
    minConn = 1
    maxConn = 10
    maxTotal = 100
    queryLimit = 100
  }
}

数据库中,我们需要建几个seata的表

建表语句地址:https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql

  • global_table : 全局事务会话表

  • branch_table: 分支事务会话表

  • lock_table: 锁数据表

也可以从我这里粘:

复制代码
-- the table to store GlobalSession data
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 = InnoDB
  DEFAULT 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 = InnoDB
  DEFAULT 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),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;
2.2.4.nacos配置中心替换file.conf

本地启动可以不做这一步,seata集群,或者企业项目,一般会用nacos的配置中心而非本地file.conf文件

在1.2.2中,我们进行了nacos的配置,其中有一个config{ dataId: "xxxx" }

我们可以在配置中心的配置管理里,加一个对应的配置,来使用配置中心而非本地文件配置

内容如下:

复制代码
#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


#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=file
store.session.mode=file


#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://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
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



#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

每个属性的含义可以在seata官网查到,太多了这里就不一个个介绍了

2.3. 启动

启动步骤为,先启动nacos然后在启动Seata-Server

启动Seata-Server的方式非常简单,直接双击此文件即可:seata-server-1.4.2\bin\seata-server.bat

linux的话运行.sh

启动完成效果

然后在nacos控制台上就可以看到Seata-Server

三.微服务项目集成

3.1. 引入依赖

复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

3.2. 配置TC(Seata-server)地址

复制代码
seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    # 参考tc服务自己的registry.conf中的配置
    type: nacos
    nacos: # 用于在nacos中寻找tc
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP  # 分组名要入registry.conf中的配置一致
      application: seata-tc-server # tc服务在nacos中的服务名称
      cluster: SH                  # 对应TC的配置
  tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
  service:
    vgroup-mapping: # 事务组与TC服务cluster的映射关系
      seata-demo: SH

3.3. 模式配置

3.3.1. 使用XA模式

XA模式是通过数据库的支持达到事务的。使用xa模式,需要两个条件

1,数据库本身支持XA事务

2,java程序通过jdbc访问数据库

需要进行的配置如下:

step1: yml配置文件中增加配置:

复制代码
seata:
  data-source-proxy-mode: XA

step2:要使用事务的方法上加 @GlobalTransactional

复制代码
@Service
public class BusinessService {
 
    @GlobalTransactional
    public void doBusiness() {
        // 执行本地事务
        // ...
        // 调用远程服务
        // ...
    }
}

3.3.2. 使用AT模式

AT模式的本质是Seata 的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前的数据镜像组织成回滚日志,利用 本地事务的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个本地事务中提交。

具体操作步骤如下:

step1: yml配置文件中增加配置:

复制代码
seata:
    data-source-poxy-mode: AT

step2:每个服务的数据库增加一个表:

复制代码
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`)
) 

step3:Service层增加注解

复制代码
@Service
public class BusinessService {
 
    @GlobalTransactional
    public void doBusiness() {
        // 执行本地事务
        // ...
        // 调用远程服务
        // ...
    }
}

3.3.2. 使用TCC模式

TCC是比较复杂的一个事务模式,它不依赖于底层数据资源的事务支持,且Try-Confirm-Cancel三者完全由开发者自行开发定义:

  • 一阶段 prepare 行为:调用 自定义 的 Try逻辑。
  • 二阶段 commit 行为:调用 自定义的 Confirm 逻辑。
  • 二阶段 rollback 行为:调用 自定义的 Cancel 逻辑

当服务中涉及如下情况,则可考虑使用TCC模式:

  • 不支持事务的数据库与中间件(如redis)等的操作
  • AT模式暂未支持的数据库(目前AT支持Mysql、Oracle与PostgreSQL)
  • 跨公司服务(第三方服务)的调用(无法共享Seata Server)
  • 跨语言的应用调用
  • 有手动控制整个二阶段提交过程的需求

**我实际做过的项目里,没有使用TCC的,所以这东西,了解一下就好**

具体操作步骤如下:

step1:由于TCC模式不赖数据库事务能力,所以不再需要进行data-source-poxy-mode的配置,yml文件中不需要改东西

step2:编写两个服务的service,每个service都要手动编写commit,rollback,接口声明如下

服务A的service

复制代码
public interface serviceOne {
    @TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
    public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);

    public boolean commit(BusinessActionContext actionContext);

    public boolean rollback(BusinessActionContext actionContext);
}

服务B的service

复制代码
public interface serviceTwo {
    @TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
    public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);

    public boolean commit(BusinessActionContext actionContext);

    public boolean rollback(BusinessActionContext actionContext);
}

step3:编写业务主方法

复制代码
@GlobalTransactional
public String doTransactionCommit(){
    //服务A事务参与者
    serviceOne.prepare(null,"one");
    //服务B事务参与者
    serviceTwo.prepare(null,"two");
}

3.3.2. 使用SAGA模式

官方介绍:Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

个人理解:saga其实类似TCC,开发者自己来维护事务的提交和失败补偿,但是saga引入了类似流程引擎的东西,通过定义流程来确定成功,失败该使用对应的什么方式来处理,sage可以视作一种TCC的增强(TCC+引擎模板)。

这个模式我没有在实际开发中用过,建议大家看这位大佬的:

springcloud+eureka整合阿里seata-saga模式_51CTO博客_spring cloud整合seata

稍微总结一下,就是

step1:定义状态机(就是一个json文件)

step2:增加三张库表,用于维护状态机状态

step3:类似于TCC模式,自己变成写事务的提交,回滚方法

相关推荐
盖世英雄酱581365 小时前
Java 组长年终总结:靠 AI 提效 50%,25 年搞副业只赚 4k?
后端·程序员·trae
+VX:Fegn08956 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
code bean6 小时前
Flask图片服务在不同网络接口下的路径解析问题及解决方案
后端·python·flask
+VX:Fegn08956 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑6 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
颜淡慕潇8 小时前
深度解析官方 Spring Boot 稳定版本及 JDK 配套策略
java·后端·架构
Victor3568 小时前
Hibernate(28)Hibernate的级联操作是什么?
后端
Victor3568 小时前
Hibernate(27)Hibernate的查询策略是什么?
后端
飞鸟真人9 小时前
Redis面试常见问题详解
数据库·redis·面试
superman超哥9 小时前
Rust 内部可变性模式:突破借用规则的受控机制
开发语言·后端·rust·rust内部可变性·借用规则·受控机制