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模式,自己变成写事务的提交,回滚方法

相关推荐
MG_鹏鹏3 分钟前
SpringBoot实现单文件上传
java·spring boot·spring
ZZZ_zzz55524 分钟前
Spring—Bean工厂进一步学习
javascript·学习·spring
fa_lsyk1 小时前
Spring:AOP面向切面编程入门案例
java·后端·spring
bin91531 小时前
【热门主题】000062 云原生后端:开启高效开发新时代
后端
Ling_suu2 小时前
SpringMVC——请求和响应
spring
ZERO空白2 小时前
Spring MVC:原理、配置与基础应用详解
java·spring·mvc
nameofworld10 小时前
前端面试笔试(六)
前端·javascript·面试·学习方法·递归回溯
哎呦没10 小时前
Spring Boot OA:企业办公自动化的高效路径
java·spring boot·后端
前端fighter10 小时前
js基本数据新增的Symbol到底是啥呢?
前端·javascript·面试
真心喜欢你吖10 小时前
Spring Boot与MyBatis-Plus的高效集成
java·spring boot·后端·spring·mybatis