分布式事务seata的使用_seata事务管理-程序员宅基地

技术标签: wpf  

分布式事务介绍


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

两阶段提交协议(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

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/mrlin6688/article/details/134466697

智能推荐

什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?_成员内部类和局部内部类的区别-程序员宅基地

文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别

分布式系统_分布式系统运维工具-程序员宅基地

文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具

用Exce分析l数据极简入门_exce l趋势分析数据量-程序员宅基地

文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量

宁盾堡垒机双因素认证方案_horizon宁盾双因素配置-程序员宅基地

文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置

谷歌浏览器安装(Win、Linux、离线安装)_chrome linux debian离线安装依赖-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖

烤仔TVの尚书房 | 逃离北上广?不如押宝越南“北上广”-程序员宅基地

文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...

随便推点

java spark的使用和配置_使用java调用spark注册进去的程序-程序员宅基地

文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序

汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用_uds协议栈 源代码-程序员宅基地

文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码

AUTOSAR基础篇之OS(下)_autosar 定义了 5 种多核支持类型-程序员宅基地

文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型

VS报错无法打开自己写的头文件_vs2013打不开自己定义的头文件-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件

【Redis】Redis基础命令集详解_redis命令-程序员宅基地

文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令

URP渲染管线简介-程序员宅基地

文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线