分布式事务seata的使用

news2024/11/24 7:21:43

分布式事务介绍


在微服务架构中,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一 致性。

两阶段提交协议(2PC)


角色


TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。

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

其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。

概念


两阶段提交(Two Phase Commit),就是将提交(commit)过程划分为两个阶段(Phase):

  • 阶段1

TM通知各个RM准备提交它们的事务分支。如果RM判断自己进行的工作可以被提交,那就对工作内容进行持久化,再给TM肯定答复;要是发生了其他情况,那给TM的都是否定答复。
以mysql数据库为例,在第一阶段,事务管理器向所有涉及到的数据库服务器发出==prepare"准备提交"请求,数据库收到请求后执行数据修改和日志记录等处理,处理完成后只是把事务的状态改成commit"可以提交"==,然后把结果返回给事务管理器。

  • 阶段2

TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare失败的话,则TM通知所有RM回滚自己的事务分支。
以mysql数据库为例,如果第一阶段中所有数据库都prepare成功,那么事务管理器向数据库服务器发出=="确认提交"请求,数据库服务器把事务的"可以提交"状态改为"提交完成"状态==,然后返回应答。如果在第一阶段内有任何一个数据库的操作发生了错误,或者事务管理器收不到某个数据库的回应,则认为事务失败,回撤所有数据库的事务。数据库服务器收不到第二阶段的确认提交请求,也会 把=="可以提交"的事务回撤==


两阶段提交方案下全局事务的ACID特性,是依赖于RM的。一个全局事务内部包含了多个独立的事务分支,这一组事务分支要么都成功,要么都失败。各个事务分支的ACID特性共同构成了全局事务的ACID特性。也就是将单个事务分支支持的ACID特性提升一个层次到分布式事务的范畴。

2PC 存在的问题

同步阻塞问题

2PC 中的参与者是阻塞的。在第一阶段收到请求后就会预先锁定资源,一直到 commit 后才会释放。

单点故障

由于协调者的重要性,一旦协调者TM发生故障,参与者RM会一直阻塞下去。尤其在第二阶段, 协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。

数据不一致

若协调者第二阶段发送提交请求时崩溃,可能部分参与者收到commit请求提交了事务,而另一 部分参与者未收到commit请求而放弃事务,从而造成数据不一致的问题。

Seata 分布式事务框架


简介


Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global Transaction Service 全局事务服务)

官网:https://seata.io/zh-cn/index.html

源码: https://github.com/seata/seata

Seata 分布式事务生命周期


在 Seata 中,一个分布式事务的生命周期如下:

  • TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID会在微服 务的调用链路中传播,保证将多个微服务的子事务关联在一起。
  • RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联。
  • TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
  • TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。

SQL限制


Seata 事务目前支持 INSERT、UPDATE、DELETE 三类 DML 语法的部分功能,这些类型都是已经经过 Seata 开源社区的验证。SQL 的支持范围还在不断扩大,建议在本文限制的范围内使用。如果您有意帮助社区支持更多类型的 SQL,请提交 PR 申请。

使用限制

  • 不支持 SQL 嵌套
  • 不支持多表复杂 SQL(自1.6.0版本,MySQL支持UPDATE JOIN语句,详情请看 )
  • 不支持存储过程、触发器
  • 部分数据库不支持批量更新,在使用 MySQL、Mariadb、PostgreSQL9.6+作为数据库时支持批量,批量更新方式如下以 Java 为例
    // use JdbcTemplate
    public void batchUpdate() {
        jdbcTemplate.batchUpdate(
            "update storage_tbl set count = count -1 where id = 1",
            "update storage_tbl set count = count -1 where id = 2"
		);
    }

    // use Statement
    public void batchUpdateTwo() {
        statement.addBatch("update storage_tbl set count = count -1 where id = 1");
        statement.addBatch("update storage_tbl set count = count -1 where id = 2");
        statement.executeBatch();
    }

快速开始


下载:seata.io

Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。

资源目录:

https://github.com/seata/seata/tree/v1.5.1/script

  • client :存放client端sql脚本,参数配置
  • config-center :存放各个配置中心参数导入脚本,config.txt(包含server和client)为通用参数文件
  • server :存放数据库脚本及各个容器配置

TC 端环境搭建


修改配置文件

配置文件的位置在/seata/config下

配置中心设置

如果nacos上的配置与application.yml有相同配置,则覆盖

示例配置

实际配置(使用nacos

nacos上传配置文件

直接官方提供的即可,\script\config-center\config.txt,复制文件内容,在nacos上按照上述配置创建配置文件

TC 端注册中心设置

实际配置(使用nacos

日志配置

存储模式配置

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

  • file :单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高
  • db :高可用模式,全局事务会话信息通过db共享,相应性能差些
  • 目前仅支持 mysql、oracle、postgresql
  • redis :1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置适合当前场景的redis持久化配置

实际配置(使用db)

在naocs配置中心修改配置文件

由于seata是通过jdbc的executeBatch来批量插入全局锁的,根据MySQL官网的说明,连接参数中的rewriteBatchedStatements为true时,在执行executeBatch,并且操作类型为insert时,jdbc驱动会把对应的SQL优化成insert into () values (), ()的形式来提升批量插入的性能。根据实际的测试,该参数设置为true后,对应的批量插入性能为原来的10倍多,因此在数据源为MySQL时,建议把该参数设置为true。

创建需要的表(mysql)

在配置中的数据库下,执行\seata\script\server\db\mysql.sql文件即可

事务分组设置

配置事务分组, 之后client端配置需与其的事务分组一致

  • 事务分组:seata的资源逻辑,可以按微服务的需要,在应用程序(客户端)对自行定义事务分组,每组取一个名字。
  • 集群:seata-server服务端一个或多个节点组成的集群cluster。 应用程序(客户端)使用时需要指定事务逻辑分组与Seata服务端集群的映射关系。

在naocs配置中心修改配置文件

事务分组如何找到后端Seata集群(TC)?

  • 首先应用程序(客户端)中配置了事务分组(GlobalTransactionScanner 构造方法的txServiceGroup参数)。若应用程序是SpringBoot则通过seata.tx-service-group配置。
  • 应用程序(客户端)会通过用户配置的配置中心去寻找service.vgroupMapping .[事务分组配置项],取得配置项的值就是TC集群的名称。若应用程序是SpringBoot则通过seata.service.vgroup-mapping.事务分组名=集群名称 配置
  • 拿到集群名称程序通过一定的前后缀+集群名称去构造服务名,各配置中心的服务名实现不同(前提是Seata-Server已经完成服务注册,且Seata-Server向注册中心报告cluster名与应用程序(客户端)配置的集群名称一致)
  • 拿到服务名去相应的注册中心去拉取相应服务名的服务列表,获得后端真实的TC服务列表(即Seata-Server集群节点列表)

启动Seata Server

双击/bin/seata-server.bat启动

nacos 上注册成功

支持的启动参数

 


比如:

bin/seata-server.sh -p 8091 -h 127.0.0.1 -m db

Client(TM / RM) 端搭建(AT为例)

AT模式依赖于seataundo_log回滚日志,来进行事务的回滚。所以需要为每个微服务的对应的库创建undo_log日志表(如果都在同一个库则仅需要一张undo_log表即可)。

-- 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 = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

TM 端

maven 依赖
 

<dependencies>

    <!-- web容器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- nacos 注册中心-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- openFeign -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        <scope>provided</scope>
    </dependency>

    <!--Mysql数据库驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- druid连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
    </dependency>

    <!-- seata 分布式事务 -->
    <!-- 这里需要使用整合包,其实现了TM通过feign将xid向下传递(TM端关注) -->
    <!-- 不使用整合包也可以自实现restTemplate扩展的client接口,实现xid向下传递 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <exclusions>
            <exclusion>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--升级使用1.6.0版本-->
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.6.0</version>
    </dependency>


    <!-- MyBatis-plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>

</dependencies>

application.xml 配置

server:
  port: 7100

spring:
  application:
    name: xa-order
  cloud:
    # 应用自身nacos注册地址
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        password: nacos
        username: nacos
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: root

# seata配置需保持和server端一致
seata:
  application‐id: ${spring.application.name}
  # 数据源代理模式 默认AT
  data-source-proxy-mode: XA
  # seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应
  tx‐service‐group: default_tx_group
  # TC注册中心配置
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: ad50a46c-e62f-4aa6-9ad2-1b1edbaaee03
  client:
    # tm端配置
    tm:
      # 一阶段全局提交结果上报TC重试次数 (配置值使用的是默认值)
      commit-retry-count: 5
      # 一阶段全局回滚结果上报TC重试次数 (配置值使用的是默认值)
      rollback-retry-count: 5
      # 全局事务超时时间 (配置值使用的是默认值)
      default-global-transaction-timeout: 6000
      # TM全局事务拦截器顺序 (配置值使用的是默认值)
      # 保证拦截器在本地事务拦截器之前执行,也可自定义全局事务和业务开发的拦截器执行顺序
      interceptor-order: -2147482648
      # 分布式事务降级开关
      degrade-check: false

业务代码有@GlobalTransactional的即为TM

RM 端

RM端和TM端一致,如果不充当TM则不需要使用cloud整合依赖

<!-- seata 分布式事务 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.6.0</version>
</dependency>

事务模式


AT 模式(推荐)


前提
基于支持本地 ACID 事务的关系型数据库。
Java 应用,通过 JDBC 访问数据库。

整体机制


两阶段提交协议的演变:

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

设计思路


Seata AT模式的核心是对业务无侵入,是一种改进后的两阶段提交,其设计思路如下:

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

一阶段

业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。核心在于对业务sql进行解析,转换成undo_log,并入库。

二阶段

分布式事务操作成功,则TC通知RM异步删除undo_log

分布式事务操作失败,TMTC发送回滚请求,RM收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。

大致流程

  • TM发起全局事务开启请求TC,TC返回XID,并构建全局事务信息存储到global_table表中。
  • TM端执行业务方法,并将XID向下游远程调用传递。
  • 各个RM端执行自身的事务方法,并记录到seata的undo_log中。并向TC提交一阶段的分支事务记录
  • 整个过程无异常,TM发起全局事务提交请求TC。
  1. TC释放全局锁,删除对应的全局锁记录,更新全局事务状态。
  2. TC向各分支事务发起异步二阶段分支事务提交。
  3. 各RM异步提交删除之前的undo_log日志任务到队列,返回二阶段提交完成状态。
  • 整个过程出现异常,TM发起全局事务回滚请求TC。
  1. TC修改全局事务状态:Begin—>Rollbacking
  2. TC向各分支事务发起远程调用,通知RM删除对应undo_log
  3. 各RM收到通知,校验undo_log(数据的前后镜像对比)

        成功:分支事务二阶段回滚成功
        失败:分支事务二阶段回滚失败
                镜像不一致失败,不进行重试
                网络等其他原因,进行重试

   4.TC收到各分支事务的响应,返回全局事务状态给TM

  • TM收到TC响应的全局事务状态,若则失败根据事务状态进行处理

快速使用


按照上面的 Client端搭建即可。AT模式对业务代码几乎无侵入,仅需要在对应的TM端加上@GlobalTransactional注解即可。

回滚失败异常处理


Seata提供了 FailureHandler 可扩展接口,可以让开发自行处理一些提交或回滚失败后的处理。
作用于 TM端,利用各分支事务二阶段处理结果返回给TC,TC再将二阶段事务执行结果返回给TM。

import io.seata.tm.api.DefaultFailureHandlerImpl;
import io.seata.tm.api.FailureHandler;
import io.seata.tm.api.GlobalTransaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
@Slf4j
public class SeataFailureHandlerConfig {

    @Bean
    public FailureHandler failureHandler(){
        return new EmailSeataFailureHandler();
    }

    class EmailSeataFailureHandler extends DefaultFailureHandlerImpl {

        @Override
        public void onBeginFailure(GlobalTransaction tx, Throwable cause) {
            super.onBeginFailure(tx, cause);
            log.warn("邮件通知:分布式事物出现异常:[onBeginFailure],xid:[{}]", tx.getXid());
        }

        @Override
        public void onCommitFailure(GlobalTransaction tx, Throwable cause) {
            super.onCommitFailure(tx, cause);
            log.warn("邮件通知:分布式事物出现异常:[onCommitFailure],xid:[{}]", tx.getXid());
        }

        @Override
        public void onRollbackFailure(GlobalTransaction tx, Throwable originalException) {
            super.onRollbackFailure(tx, originalException);
            log.warn("邮件通知:分布式事物出现异常:[onRollbackFailure],xid:[{}]", tx.getXid());
        }

        @Override
        public void onRollbackRetrying(GlobalTransaction tx, Throwable originalException) {
            super.onRollbackRetrying(tx, originalException);
            log.warn("邮件通知:分布式事物出现异常:[onRollbackRetrying],xid:[{}]", tx.getXid());
        }
    }
}

注意事项


AT模式的回滚利用的是undo_log日志,所以如果在undo_log生成后,回滚前,这条数据被其他业务或人为所修改,则无法进行回滚(前后镜像不一致);且全局事务锁不会释放,那么意味着这条业务线在数据修正前将一直无法使用。

解决方案

利用上述的重写FailureHandler进行邮件、短信等通知人为及时处理
避免在全局事务执行期间RM端涉及业务数据被其他业务修改
如果是同方法下该RM必须被多个业务调用,则可以在该业务方法上使用@GlobalTransactional注解进行全局事务锁控制,这样就控制业务逐一执行,避免脏数据产生。
————————————————

XA 事务模式

前提

  • 支持 XA 事务的数据库。
  • Java 应用,通过 JDBC 访问数据库。

整体机制

在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。

执行阶段:


*
可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚
持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)

完成阶段:


*
分支提交:执行 XA 分支的 commit
分支回滚:执行 XA 分支的 rollback

详见官网介绍

与 AT 模式的区别


AT的前提是支持ACID的关系型数据库。
XA的前提是支持XA事务的数据库。
XA模式的分支事务会一直等待TM向TC响应执行结果,再进行回滚或提交,在这期间由事务产生的锁是一直占用资源的。
AT模式则是异步化的,根据undo_log进行回滚。
XA的分支事务注册由TC统一生成的,所以 XA 模式分支注册的时机需要在 XA start 之前(未来也许会向AT模式一样)。
AT 模式则是在本地事务提交之前才注册分支,可以避免分支执行失败的情况下进行无意义的分支注册。
XA模式代码需要利用本地事务@Transactional注解,否则方法内的本地事务执行sql会有事务冲突,造成死锁


快速使用


相比较 AT 模式需要做的改动仅有两点:

TM端需要加上Spring的@Transactional注解
 

@Override
@Transactional
@GlobalTransactional(name="createOrder",rollbackFor=Exception.class)
public Order saveOrder(OrderVo orderVo, Boolean hasException) {
    log.info("=============用户下单=================");
    log.info("当前 XID: {}", RootContext.getXID());
    // ...
}

yml配置文件中指定数据源代理模式为 XA

# seata配置需保持和server端一致
seata:
  application‐id: ${spring.application.name}
  # 数据源代理模式 默认AT
  data-source-proxy-mode: XA
  # seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应
  tx‐service‐group: default_tx_group

TCC 模式


前提


TCC 模式,不依赖于底层数据资源的事务支持。它是一种手动控制的模式,可以应用在各种数据库中,例:Redis等。

整体机制


一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

  • 一阶段 prepare 行为
  • 二阶段 commit 或 rollback 行为

TCC 模式不依赖于底层数据资源的事务支持:

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

    TCC 异常控制机制

  • TCC 模式是分布式事务中非常重要的事务模式,但是幂等、悬挂和空回滚一直是 TCC 模式需要考虑的问题。

    Seata 的做法是在客户端新增一个 TCC 事务控制表( tcc_fence_log ),里面记录了分支事务一阶段操作的 XID、BranchId 及执行状态等,后续会根据这个记录处理各异常。

    空回滚

    空回滚指的是在一个分布式事务中,在没有调用参与方的 Try 方法的情况下,TM 驱动二阶段回滚调用了参与方的 Cancel 方法。

    产生原因

如上图所示,全局事务开启后,参与者 A 分支注册完成之后会执行参与者一阶段 RPC 方法,如果此时参与者 A 所在的机器发生宕机、网络异常,都会造成 RPC 调用失败,即参与者 A 一阶段方法未成功执行,但是此时全局事务已经开启,Seata 必须要推进到终态,在全局事务回滚时会调用参与者 A 的 Cancel 方法,从而造成空回滚。

解决

根据 tcc_fence_log 的记录,在执行 Cancel / Rollback 方法时读取这条记录,如果记录不存在,说明 Try 方法没有执行。

幂等

幂等问题指的是 TC 重复进行二阶段提交,因此 Cancel 接口需要支持幂等处理,即不会产生资源重复提交或者重复释放。

产生原因

如上图所示,参与者 A 执行完二阶段之后,由于网络抖动或者宕机问题,会造成 TC 收不到参与者 A 执行二阶段的返回结果,TC 会重复发起调用,直到二阶段执行结果成功。

解决

根据 tcc_fence_log 记录状态的字段 status,该字段有 4 个值,分别为:

tried:1
committed:2
rollbacked:3
suspend:4
二阶段 Confirm / Cancel 方法执行后,将状态改为 committed 或 rollbacked 状态。当重复调用二阶段 Confirm/Cancel 方法时,判断事务状态即可解决幂等问题。

悬挂

悬挂指的是二阶段 Cancel 方法比 一阶段 Try 方法优先执行,由于允许空回滚的原因,在执行完二阶段 Cancel 方法之后直接空回滚返回成功,此时全局事务已结束,但是由于 Try 方法随后执行,这就会造成一阶段 Try 方法预留的资源永远无法提交和释放了。

产生原因

如上图所示,在执行参与者 A 的一阶段 Try 方法时,出现网路拥堵,由于 Seata 全局事务有超时限制,执行 Try 方法超时后,TM 决议全局回滚,回滚完成后如果此时 RPC 请求才到达参与者 A,执行 Try 方法进行资源预留,从而造成悬挂。

解决

当执行二阶段 Cancel 方法时,如果发现 TCC 事务控制表有相关记录,说明二阶段 Cancel 方法优先一阶段 Try 方法执行,因此插入一条 status=4 状态的记录,当一阶段 Try 方法后面执行时,判断 status=4 ,则说明有二阶段 Cancel 已执行,并返回 false 以阻止一阶段 Try 方法执行。

与 AT 等模式的区别

  • AT等模式需要依赖数据库的事务特性
  • TCC模式则是通过调用自定义的逻辑进行事务控制
  • AT等模式对业务的侵入几乎为0
  • TCC模式由于是调用自定义的逻辑,所以对业务有较大的的侵入
  • AT模式利用了undo_log的镜像记录进行自动回滚处理
  • TCC模式调用自定义的回滚逻辑,不过针对一些特殊的异常利用了tcc_fence_log 记录表
     

快速使用


TCC模式与之前两种的模式实现上有较大的区别,主要在于其是通过调用自定义的逻辑去实现事务控制

客户端建立 tcc_fence_log 表

TCC模式对一些特殊的异常处理依赖于tcc_fence_log表,所以需要在各客户端建立该表。(若不考虑这些特殊异常可以不使用)
 

-- 创建 tcc_fence_log 表,支持 tcc 解决空回滚、悬挂、幂等问题
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 = utf8mb4;

配置文件中也可以指定表名

# seata配置需保持和server端一致
seata:
  application‐id: ${spring.application.name}
  # seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应
  tx‐service‐group: default_tx_group
  # tcc 配置
  tcc:
    fence:
      # 表名配置
      log-table-name: tcc_fence_log

TCC 接口编写

TCC模式依赖于自定义逻辑,所以需要自己去定义一阶段的预提交,二阶段的回滚/提交方法

注意: commit 和 rollback方法必须有BusinessActionContext,否则无法获取到上下文数据(除非不需要利用上下文数据)

import cn.zh.order.domain.vo.OrderVo;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

/**
 * @author zh
 * OrderService对应的Tcc接口
 */
@LocalTCC
public interface OrderServiceSeataTcc {

    /**
     * TCC的try方法:保存订单信息,状态为支付中
     *
     * 定义两阶段提交,在try阶段通过@TwoPhaseBusinessAction注解定义了分支事务的 resourceId,commit和 cancel 方法
     *  name = 该tcc的bean名称,全局唯一
     *  commitMethod = commit 为二阶段确认方法
     *  rollbackMethod = rollback 为二阶段取消方法
     *  BusinessActionContextParameter注解 传递参数到二阶段中
     *  useTCCFence seata1.5.1的新特性,用于解决TCC幂等,悬挂,空回滚问题,需增加日志表tcc_fence_log
     *
     * @param orderVo
     * @return
     */
    @TwoPhaseBusinessAction(name = "prepareSaveOrder", commitMethod = "commit", rollbackMethod = "rollback", useTCCFence = true)
    Integer prepareSaveOrder(OrderVo orderVo);

    /**
     *
     * TCC的confirm方法:订单状态改为支付成功
     *
     * 二阶段确认方法可以另命名,但要保证与commitMethod一致
     * context可以传递try方法的参数
     *
     * @param actionContext
     * @return
     */
    void commit(BusinessActionContext actionContext);

    /**
     * TCC的cancel方法:订单状态改为支付失败
     * 二阶段取消方法可以另命名,但要保证与rollbackMethod一致
     *
     * @param actionContext
     * @return
     */
    boolean rollback(BusinessActionContext actionContext);

}

注解 / 参数说明

@LocalTCC
声明TCC接口Bean。适用于SpringCloud+Feign模式下,@LocalTCC需要注解在接口上,此接口可以是寻常的业务接口(并不需要一定独立),只要实现了TCC的两阶段提交对应方法便可。

@TwoPhaseBusinessAction
作用于一阶段预提交方法,常用参数:

name:当前tcc方法的bean名称,需保证全局唯一
commitMethod:指向当前接口中的提交方法,默认方法名 commit
rollbackMethod:指向当前接口的回滚方法,默认方法名 rollback
定义完三个方法后,seata会根据全局事务的成功或失败,去帮我们自动调用提交方法或者回滚方法。

@BusinessActionContextParameter
作用于预提交方法的参数上,被注解的参数会被传入 BusinessActionContext(TCC的业务上下文),这个类会被自动传递到另外两个方法

BusinessActionContext
TCC的业务上下文,内部利用Map结构对数据存储。

 

public class BusinessActionContext implements Serializable {

    private static final long serialVersionUID = 6539226288677737991L;

    private String xid;

    private String branchId;

    private String actionName;

    private Boolean isDelayReport;



      private Boolean isUpdated;


      private Map<String, Object> actionContext;
      
      // ...
  }

此外数据传递是利用了BusinessActionContextUtil,这个工具类利用ThreadLocal对BusinessActionContext进行数据传递,所以我们也可不使用@BusinessActionContextParameter,而直接使用BusinessActionContextUtil进行参数传递
 

public final class BusinessActionContextUtil {

    private BusinessActionContextUtil() {
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(BusinessActionContextUtil.class);

    // ThreadLocal
    private static final ThreadLocal<BusinessActionContext> CONTEXT_HOLDER = new ThreadLocal<>();

    public static boolean addContext(String key, Object value) {
        if (value == null) {
            return false;
        }

        Map<String, Object> newContext = Collections.singletonMap(key, value);
        return addContext(newContext);
    }
    
    // ...
}

// 例子
@Override
public Integer prepareSaveOrder(OrderVo orderVo) {

    // 保存订单
    Order order = new Order();
    order.setUserId(orderVo.getUserId());
    order.setCommodityCode(orderVo.getCommodityCode());
    order.setCount(orderVo.getCount());
    order.setMoney(orderVo.getMoney());
    order.setStatus(OrderStatus.INIT.getValue());
    Integer saveOrderRecord = orderMapper.insert(order);
    log.info("保存订单{}", saveOrderRecord > 0 ? "成功" : "失败");

    // 内部添加上下文数据
    BusinessActionContextUtil.addContext("orderId", order.getId());
    return saveOrderRecord;
}

TM 端方式使用 @GlobalTransactional 注解

@Override
@GlobalTransactional(name="createOrder",rollbackFor=Exception.class)
public void saveOrder(OrderVo orderVo, Boolean hasException) {
    // ...
}

Saga模式


Saga模式通常适用于业务流程长或多的场景。
这里不过多介绍,详见官网:https://seata.io/zh-cn/docs/user/saga.html

其他补充
除 AT 模式外二阶段只有成功 / 失败重试两种逻辑?

Seata设计认为二阶段应处于最终一致性状态,当不一致时理应就不断的去重试直到成功,并且二阶段并不应该考虑各种异常情况,异常应当只会有数据库连接不上这种情况。针对这种情况做法应该是监控系统及时报警然后及时恢复数据库的连接。
对于TCC模式二阶段的处理不应该有太多的复杂逻辑存在;若是程序的编码错误,不应该考虑在内。
对于AT模式由于其二阶段回滚依赖于undo_log镜像所以提供了失败异常处理。

TCC 模式异常重试机制


在TCC模式中如果二阶段出现异常,则会每秒一次去不断重试(1.6.0,后续或会增加重试间隔配置),即使服务重启也会去不断重试;这是根据server端记录的事务状态去驱动执行客户端二阶段方法,在server端branch_table中会记录此次的上下文信息,并传递执行二阶段。

示例项目代码地址
gitee:https://gitee.com/ahang-gitee/learn-seata

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1220608.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习_14_单层|多层感知机及代码实现

单层感知机&#xff1a; 功能&#xff1a; 能完成二分类问题 问题&#xff1a; 模型训练采用X*W b训练出模型&#xff0c;对数据只进行了一层处理&#xff0c;也就是说训练出来的模型仅是个线性模型&#xff0c;它无法解决XOR问题&#xff0c;所以模型在训练效果上&#xf…

cookie机制 + java 案例

目录 为什么会有cookie?? cookie从哪里来的&#xff1f;&#xff1f; cookie到哪里去&#xff1f;&#xff1f; cookie有啥用&#xff1f;&#xff1f; session HttpServletRequest类中的相关方法 简单的实现cookie登录功能 实现登录页面 实现servlet逻辑 实现生成主…

【Spring】依赖注入方式,DI的方式

这里写目录标题 1. setter注入在一个类中注入引用类型在一个类中注入简单类型 2. 构造器注入在一个类中注入引用类型在一个类中注入简单类型 3. 依赖注入方式选择4. 依赖自动装配按类型注入按名称注入 5. 集合注入 1. setter注入 在一个类中注入引用类型 回顾一下之前setter注…

Python基础:输入输出详解-输出字符串格式化

Python中的输入和输出是编程中非常重要的方面。 1. 输入输出简单介绍 1.1 输入方式 Python中的输入可以通过input()函数从键盘键入&#xff0c;也可以通过命令行参数或读取文件的方式获得数据来源。 1&#xff09;input()示例 基本的input()函数&#xff0c;会将用户在终端&…

力扣栈与队列--总结篇

前言 八道题&#xff0c;没想到用了五天。当然需要时间的沉淀&#xff0c;但是一天不能啥也不干啊&#xff01; 内容 首先得熟悉特点和基本操作。 栈与队列在计算机底层中非常重要&#xff0c;这就是为什么要学好数据结构。 可视化的软件例如APP、网站之类的&#xff0c;都…

Kotlin原理+协程基本使用

协程概念 协程是Coroutine的中文简称&#xff0c;co表示协同、协作&#xff0c;routine表示程序。协程可以理解为多个互相协作的程序。协程是轻量级的线程&#xff0c;它的轻量体现在启动和切换&#xff0c;协程的启动不需要申请额外的堆栈空间&#xff1b;协程的切换发生在用…

AVL树的底层实现

文章目录 什么是AVL树&#xff1f;平衡因子Node节点插入新节点插入较高左子树的左侧新节点插入较高左子树的右侧新节点插入较高右子树的左侧新节点插入较高右子树的右侧 验证是否为平衡树二叉树的高度AVL的性能 什么是AVL树&#xff1f; AVL树又称平衡二叉搜索树&#xff0c;相…

腾讯云服务器便宜吗?腾讯云服务器怎么买便宜?附优惠链接

首先&#xff0c;咱们来看一下大家最关心的一个问题&#xff1a;“腾讯云服务器便宜吗&#xff1f;”我的答案是&#xff1a;“YES&#xff01;它真的很便宜&#xff01;”比如&#xff0c;轻量2核2G3M服务器&#xff0c;1年只需要88元&#xff0c;是不是很划算&#xff1f;再比…

实例解释遇到前端报错时如何排查问题

前端页面报错&#xff1a; 1、页面报错500&#xff0c;首先我们可以知道是服务端的问题&#xff0c;需要去看下服务端的报错信息&#xff1a; 2、首先我们查看下前端是否给后端传了id: 我们可以看到接口是把ID返回了&#xff0c;就需要再看下p_id是什么情况了。 3、我们再次请…

基于ssm+vue的程序设计课程可视化教学系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

双11背后的中国云厂商:新“标准化”,和调整后的新韧性

降价并不代表一味的压缩自身利润空间&#xff0c;云厂商已经开始向具有更高利润空间的PaaS、SaaS产品腾挪&#xff0c;核心产品在总包占比越来越高。 作者|斗斗 编辑|皮爷 出品|产业家 今年云厂商&#xff0c;全面拥抱双11。 作为中国最大的云计算服务提供商&#xff0…

关于苏州立讯公司国产替代案例(使用我公司H82409S网络变压器和E1152E01A-YG网口连接器产品)

关于苏州立讯公司国产替代案例&#xff08;使用我们公司的H82409S网络变压器和E1152E01A-YG网口连接器产品&#xff09; 苏州立讯公司是一家专注于通信设备制造的企业&#xff0c;他们在其产品中选择了我们公司的H82409S网络变压器和E1152E01A-YG网口连接器&#xff0c;以实现…

在线协作工具都有哪些?推荐这10款

如今&#xff0c;互联网的快速发展不仅改变了我们的生活方式&#xff0c;也改变了我们的工作方式。 特别是对于一些与产品设计相关的公司或团体&#xff0c;网络不仅为其设计提供了稳定的资源和灵感&#xff0c;而且为成员之间的沟通和合作提供了更大的便利。 如果您也需要为…

ke11介绍本地,会话存储

代码顺序: 1.设置input,捕获input如果有多个用属性选择符例如 input[typefile]点击事件.向我们的本地存储设置键值对 2.在点击事件外面设置本地存储表示初始化的值.点击上面的事件才能修改我们想修改的值 会话(session)浏览a数据可以写到本地硬盘,关闭页面数据就没了 本地(…

JPA整合Sqlite解决Dialect报错问题, 最新版Hibernate6

前言 我个人项目中&#xff0c;不想使用太重的数据库&#xff0c;而内嵌数据库中SQLite又是最受欢迎的&#xff0c; 因此决定采用这个数据库。 可是JPA并不支持Sqlite&#xff0c;这篇文章就是记录如何解决这个问题的。 原因 JPA屏蔽了底层的各个数据库差异&#xff0c; 但是…

使用Python的time库来格式化时间

目录 一、引言 二、time库简介 三、使用time库来格式化时间 四、使用time库进行时间戳转换 五、使用time库获取当前时间 六、使用time库进行延时操作 七、使用time库计算时间差 八、使用time库获取系统时间 九、使用time库的其他功能 总结 一、引言 在Python中&…

鸿蒙OS应用开发初体验

什么是HarmonyOS&#xff1f; HarmonyOS&#xff08;鸿蒙操作系统&#xff09;是华为公司开发的一款基于微内核的分布式操作系统。它是一个面向物联网&#xff08;IoT&#xff09;时代的全场景操作系统&#xff0c;旨在为各种类型的设备提供统一的操作系统平台和开发框架。Har…

自媒体剪辑必备,6个音效素材网站,你值得拥有。

这6个剪辑必备的音效素材网站一定要收藏好了&#xff0c;有了这几个网站能让你在剪辑的时候事半功倍&#xff0c;还不用担心版权问题。话不多说&#xff0c;直接上干货。 1、菜鸟图库 https://www.sucai999.com/audio.html?vNTYwNDUx 菜鸟图库是一个综合性素材网站&#xff…

ref属性

Vue 中 可以给标签&#xff08;HTML元素标签、组件标签&#xff09;加上一个 ref 属性&#xff0c;作用就是给 标签加上一个标识&#xff0c;可以通过该标识获取当前需要的DOM元素或者组件实例对象。 通过原生js获取DOM 如果使用原生js来获取DOM元素时&#xff0c;我们最简单的…

Kafka学习笔记(一)

目录 第1章 Kafka概述1.1 消息队列&#xff08;Message Queue&#xff09;1.1.1 传统消息队列的应用场景1.1.2 消息队列的两种模式 1.2 定义 第2章 Kafka快速入门2.1 安装部署2.1.1 集群规划2.1.2 jar包下载2.1.3 集群部署 2.2 Kafka命令行操作 第3章 Kafka架构深入3.1 Kafka工…