seata分布式事务

1.基本概述

比如开发一个web项目时,单体的事务一般都依托与数据库的事务,然而当微服务架构时往往会处于不同的数据库,所以就引出了分布式事务的概念。

CAP 定理

CAP 定理指出,在一个分布式计算系统中,不可能同时满足以下三个特性:

  1. 一致性(Consistency):所有节点看到的数据是一致的,即在任何时刻,所有节点的数据都保持一致状态。
  2. 可用性(Availability):系统提供的服务必须一直处于可用状态,即对于用户的每一个请求,系统都应该在有限的时间内做出响应。
  3. 分区容忍性(Partition tolerance):系统能够容忍网络分区的发生,即系统在遇到网络故障或延迟时仍然能够继续运行。

CPA就是三个特性的首字母组合。

那么问题来了:

  • 如果要求系统具有强一致性,即任何时刻所有节点上的数据必须保持一致,那么在进行写操作时,需要确保所有节点都能立即更新数据。但如果某个节点由于网络故障与其他节点失去联系,就会导致无法达到一致性。
  • 如果要求系统具有高可用性,即系统必须对用户请求做出及时响应,即使在网络分区的情况下也要继续提供服务,或进行写操作时,也要继续提供服务,这当然不现实。这意味着可能会出现部分节点无法及时同步数据的情况,从而降低了数据的一致性。
  • 由于网络是不可靠的,可能会发生节点之间通信受阻或延迟的分区情况。也就是网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。在集群出现分区时,整个系统也要持续对外提供服务。那么为了确保系统在面对网络分区时仍然能够继续运行,就必须牺牲一致性或可用性中的一个。
所以在一个分布式计算系统中,不可能同时满足三个特性

由此又有两种模式(基于 CAP 定理提出):

  1. AP 模式(Availability and Partition tolerance):AP 模式更加注重可用性和分区容忍性,即在面对网络分区或节点故障时,系统会优先保证可用性,即使可能会导致数据的不一致性。AP 模式适用于对于实时性和可用性要求较高的应用场景,如实时通信、互联网应用等。在 AP 模式下,系统可以继续处理请求并返回响应,即使数据在不同节点之间可能存在一段时间的不一致。比如在业务中各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
  2. CP 模式(Consistency and Partition tolerance):CP 模式更加注重一致性和分区容忍性,即在面对网络分区时,系统会优先保证数据的一致性,即使可能会影响可用性。CP 模式适用于对数据一致性要求较高的应用场景,如金融交易、核心业务系统等。在 CP 模式下,系统会等待所有节点完成数据同步后再提供服务,以保证数据的一致性。比如在业务中各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。

比如ES集群出现分区时,故障节点会被剔除集群,数据分片会重新分配到其它节点,保证数据一致。因此是低可用性,高一致性,属于CP。

 

BASE理论

BASE 理论是一种事务处理理论。BASE 是 Basically Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致性)的缩写。

  1. Basically Available(基本可用):系统在面对故障时仍然能够保持基本的可用性,即系统可以继续处理请求并返回响应,即使这些响应可能不是最新的或完全一致的。
  2. Soft state(软状态):系统中的数据在不同时间点可能会存在中间状态,并且不需要求数据的强一致性,即允许数据存在一段时间的不一致性。
  3. Eventually consistent(最终一致性):系统中的不同副本在一段时间内可能是不一致的,但最终会趋向于一致状态。即系统保证在一定时间内,所有节点最终达到一致状态。

BASE 理论强调的是在分布式系统中对可用性和性能的追求,在牺牲一致性的前提下,提供更好的性能和可用性。BASE 适用于大规模分布式系统中,例如互联网应用、大数据处理等领域,其中对于实时性和可用性要求较高,而可以容忍一定程度的数据不一致性。

 

最终解决分布式事务只有两种方法:

  • 最终一致思想:各分支事务分别执行并提交,如果有不一致的情况,采用弥补措施恢复数据即可,实现最终一致。
  • 强一致思想:各分支事务执行完业务不要提交,等待彼此结果。而后统一提交或回滚,达到强一致。

 

分布式事务模型

解决分布式事务,各个子系统之间必须能感知到彼此的事务状态,才能保证状态一致,因此需要一个事务协调者来协调每一个事务的参与者(子系统事务)。

在下图业务,下订单操作,账户资金扣除,商品库存扣除都为分支事务,而合起来就是全局事务。

 

Seata是一个开源的分布式事务解决方案,用于解决分布式系统中的事务一致性问题。

官网:Apache Seata

Seata 领域模型

Seata事务管理中有三个重要的角色:

  • TC (Transaction Coordinator)事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager)事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager)资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态并驱动分支事务提交或回滚。

 

2.搭建Seata的TC服务

下载Seata-Server下载 | Apache Seata

解压

点击conf目录下的register.conf文件,配置注册中心和配置中心

这是注册中心配置,选用哪个就保留哪个

这是配置中心配置,选用哪个就保留哪个

一般我们选用nacos,整个文件更改为

registry {
    # tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
    type = "nacos"
    nacos {
        # seata tc 服务注册到 nacos的服务名称,可以自定义
        application = "seata-tc-server"
        serverAddr = "127.0.0.1:8848"
        group = "DEFAULT_GROUP"
        namespace = ""
        cluster = "SH"
        username = "nacos"
        password = "nacos"
    }
}

config {
    # 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
    type = "nacos"
    # 配置nacos地址等信息
    nacos {
        serverAddr = "127.0.0.1:8848"
        namespace = ""
        group = "SEATA_GROUP"
        username = "nacos"
        password = "nacos"
        dataId = "seataServer.properties"
    }
}

选用了nacos作为配置中心,同时选用mysql作为db存储,就要在nacos配置文件seataServer.properties

配置参考官网:Docker compose部署 | Apache Seata

配置如下:

store.mode=db
#-----db-----
store.db.datasource=druid
store.db.dbType=mysql
# 需要根据mysql的版本调整driverClassName
# mysql8及以上版本对应的driver:com.mysql.cj.jdbc.Driver
# mysql8以下版本的driver:com.mysql.jdbc.Driver
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata-server?useUnicode=true&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=123456
# 数据库初始连接数
store.db.minConn=1
# 数据库最大连接数
store.db.maxConn=20
# 获取连接时最大等待时间 默认5000,单位毫秒
store.db.maxWait=5000
# 全局事务表名 默认global_table
store.db.globalTable=global_table
# 分支事务表名 默认branch_table
store.db.branchTable=branch_table
# 全局锁表名 默认lock_table
store.db.lockTable=lock_table
# 查询全局事务一次的最大条数 默认100
store.db.queryLimit=100


# undo保留天数 默认7天,log_status=1(附录3)和未正常清理的undo
server.undo.logSaveDays=7
# undo清理线程间隔时间 默认86400000,单位毫秒
server.undo.logDeletePeriod=86400000
# 二阶段提交重试超时时长 单位ms,s,m,h,d,对应毫秒,秒,分,小时,天,默认毫秒。默认值-1表示无限重试
# 公式: timeout>=now-globalTransactionBeginTime,true表示超时则不再重试
# 注: 达到超时时间后将不会做任何重试,有数据不一致风险,除非业务自行可校准数据,否者慎用
server.maxCommitRetryTimeout=-1
# 二阶段回滚重试超时时长
server.maxRollbackRetryTimeout=-1
# 二阶段提交未完成状态全局事务重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.committingRetryPeriod=1000
# 二阶段异步提交状态重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.asynCommittingRetryPeriod=1000
# 二阶段回滚状态重试回滚线程间隔时间 默认1000,单位毫秒
server.recovery.rollbackingRetryPeriod=1000
# 超时状态检测重试线程间隔时间 默认1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器
server.recovery.timeoutRetryPeriod=1000

数据库地址、用户名、密码都需要修改成自己的数据库信息。

注意:连接mysql那里要配置时区加上&serverTimezone=Asia/Shanghai,否则会连不上mysql

tc服务在管理分布式事务时,需要记录事务相关数据到数据库中。

同样数据库配置在官网:incubator-seata/script/server/db/mysql.sql at develop · apache/incubator-seata · GitHub

将官网的数据库文件拷贝执行即可,数据库名字seata-server要与配置文件配置名字一样

注意:branch_table和global_table是我们需要的

在bin目录下,双击bat

默认8091端口

服务列表就有了seata

在需要添加事务的服务添加依赖,注意是所有分支事务的服务都要添加

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>最新版</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.2.0及以上版本</version>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2.2.7.RELEASE</version>
</dependency>
<!--seata starter 采用1.4.2版本-->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>

还有application.yml配置

Nacos 配置中心 | Apache Seata

举例:

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    # 参考tc服务自己的registry.conf中的配置
    type: nacos
    nacos: # tc
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: DEFAULT_GROUP
      application: seata-tc-server # tc服务在nacos中的服务名称
      cluster: SH
  tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
  service:
    vgroup-mapping: # 事务组与TC服务cluster的映射关系
      seata-demo: SH

启动项目后可以看到注册进去了,我启动了一个叫hello-server的服务

 

Seata提供了四种分布式事务解决方案,用于满足不同场景下的事务一致性需求:

  1. XA模式
  2. AT(Automatic Transaction)模式
  3. TCC(Try-Confirm-Cancel)模式
  4. SAGA模式

 

3.XA模式

XA 模式是从 1.2 版本支持的事务模式。XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。

 

Seata事务管理中有三个重要的角色:

  • TC (Transaction Coordinator)事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager)事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager)资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态并驱动分支事务提交或回滚。

执行机制

1.TM往TC注册事务,开启全局事务(一阶段开始)

2.TM通知RM开启分支业务

 

3.RM向TC注册分支事务

 

4.RM执行业务代码

 

5.RM向TC报告事务状态

 

6.TM向TC报告全局事务结束(二阶段开始)

 

7.TC检查分支事务状态

 

8.如果成功就通知RM提交事务,失败就回滚

整体机制

通过TC去通知RM提交或回滚,所以要求数据库要通过接口来完成这个需求

优势

与 Seata 支持的其它事务模式不同,XA 协议要求事务资源本身提供对规范和协议的支持,所以事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。此外的一些优势还包括:

  1. 业务无侵入:XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担。
  2. 数据库的支持广泛:XA 协议被主流关系型数据库广泛支持,不需要额外的适配即可使用。

缺点

  • XA prepare 后,分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞等待。事务资源长时间得不到释放,锁定周期长,而且在应用层上面无法干预,性能差。
  • 同时依赖于数据库实现事务

 

代码使用

XA模式使用起来很简单

只要在application.yml文件加入配置

seata:
  data-source-proxy-mode: XA

然后在全局事务开启的方法加上注解

@Override
@GlobalTransactional
public Result addOrder(){
    
    //1.添加订单

    //2.资金扣除

    //3.库存扣除

    return Result.ok();
}

 

4.AT模式

XA模式分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞等待。事务资源长时间得不到释放,锁定周期长,为了解决这个缺陷就有了AT模式。

执行机制

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

两阶段提交协议的演变:

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

  • 二阶段:
    • 成功提交异步化,非常快速地完成。
    • 失败回滚通过一阶段的回滚日志进行反向补偿。

安全问题

在分支事务提交后,如果有其他业务操作该表会产生数据脏读

Seata的AT模式的解决方式:引入全局锁

全局锁:由TC记录当前正在操作某行数据的事务该事务持有全局锁,具备执行权。

  • 一阶段本地事务提交前,需要确保先拿到 全局锁 。
  • 拿不到 全局锁 ,不能提交本地事务。
  • 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁(DB锁)。

这里直接看官网的例子:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 – 100 = 900。本地事务提交前,先拿到该记录的全局锁 。本地提交释放本地锁。

tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 – 100 = 800。本地事务提交前,尝试拿该记录的全局锁 。

tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待全局锁 。

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的全局锁,同时持有本地锁。

则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 等待全局锁操作超时,放弃全局锁并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

 

成功情况下,tx1 二阶段全局提交,释放全局锁

注意情况

这里要求tx1与tx2都由seata管理,如果tx2不由Seata管理,就会直接成功,seata的解决方式是保存快照时,保存执行SQL前和执行后的数据,执行SQL前的数据用来回滚,而执行后的数据用来对比,

比如:

  1. tx2不由Seata管理直接执行成功。
  2. 在tx1再次获取全局锁成功后,会比较快照的执行后的数据与当前数据比较,如果不同证明有其他不由Seata管理的操作更新了数据,seata会记录异常,发出警告,通知人工。
注意:应使操控同一个资源要么全由Seata管理,要么不全由Seata管理。

 

使用方式

AT模式使用方式同样简单,无业务侵入。只要导入数据库两张表即可。

lock_table导入到TC服务关联的数据库,之前已经导入。没有再重新导入。

incubator-seata/script/server/db/mysql.sql at develop · apache/incubator-seata · GitHub

undo_log表导入到微服务关联的数据库:

-- 注意此处0.7.0+ 增加字段 context
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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

同时修改application.yml改为AT模式

seata:
  data-source-proxy-mode: AT

 

5.TCC模式

基本概述

TCC模式是一种补偿型的分布式事务解决方案。TCC模式通过在业务代码中定义try、confirm和cancel三个阶段的方法,手动编写补偿逻辑来实现事务的一致性。在TCC模式中,事务协调器会按照一定的规则执行相应阶段的方法,以保证全局事务的最终一致性。

TCC模式通过人工编码来实现数据恢复。需要实现三个方法:

  • Try:资源的检测和预留;
  • Confirm:完成资源操作业务;要求Try成功Confirm一定要能成功。
  • Cancel:预留资源释放,可以理解为try的反向操作。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。同时最大的优点是不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC的优点

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC的缺点

  • 代码侵入,人为编写try、Confirm和Cancel接口
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理,即考虑执行失败,重复执行会不会产生额外的影响。

空回滚

当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚。

解决方法就是执行cancel操作时,应当判断try是否已经执行,可以通过判断status状态判断try是否已经执行,如果尚未执行,则应该空回滚。

业务悬挂

对于已经空回滚的业务,如果这时阻塞结束,接着继续执行try,就永远不可能confirm或cancel,这就是业务悬挂。应当阻止执行空回滚后的try操作,避免业务悬挂

解决方法就是执行try操作时,应当判断cancel是否已经执行过了,同样可以通过判断status状态判断try是否已经执行,如果已经执行,应当阻止空回滚后的try操作,避免悬挂。

 

使用方式

假设我们的业务是资金钱扣除,那么我们可以设计一张表,记录冻结资金

CREATE TABLE `money_tcc` (
  `xid` varchar(128) NOT NULL,
  `user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
  `frozen_money` int(11) unsigned DEFAULT '0' COMMENT '冻结金额',
  `state` int(1) DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
  PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

pojo类

package cn.itcast.account.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("money_tcc")
public class MoneyTcc {
    @TableId(type = IdType.INPUT)
    private String xid;
    private String userId;
    private Integer frozenMoney;
    private Integer state;

    public static abstract class State {
        public final static int TRY = 0;
        public final static int CONFIRM = 1;
        public final static int CANCEL = 2;
    }
}

mapper层

package cn.itcast.account.mapper;

import cn.itcast.account.entity.MoneyTcc;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface MoneyTccMapper extends BaseMapper<MoneyTcc> {
}

使用方法要求定义一个接口

我们在service层定义

package com.dreams.hello.service.TCC;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

@LocalTCC
public interface AccountTCCService {

    @TwoPhaseBusinessAction(name = "prepare", commitMethod = "confirm", rollbackMethod = "cancel")
    void decreaseMoney(@BusinessActionContextParameter(paramName = "userId") String userId,
                 @BusinessActionContextParameter(paramName = "money") int money);

    boolean confirm(BusinessActionContext context);

    boolean cancel(BusinessActionContext context);
}

代码解释:

  •  @TwoPhaseBusinessAction 所修饰的方法就是执行Try逻辑,如示例代码中的 prepare 方法。
  • commitMethod 属性所指向值就是执行Confirm工作的方法名。
  • rollbackMethod 属性所指向值就是执行 Cancel 工作的方法名。
  • @BusinessActionContextParameter指定参数,可以传递到上下文中,Confirm和Cancel通过使用BusinessActionContext参数来使用

 

整体代码:

package cn.itcast.account.service.impl;

import cn.itcast.account.entity.MoneyTcc;
import cn.itcast.account.mapper.MoneyTccMapper;
import cn.itcast.account.mapper.AccountMapper;
import cn.itcast.account.service.AccountTCCService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {

    @Autowired
    //TODO 注入资金管理service


    @Autowired
    private MoneyTccMapper moneyTccMapper;

    @Override
    @Transactional
    public void decreaseMoney(String userId, int money) {
        //获取事务id
        String xid = RootContext.getXID();

        //避免业务悬挂,判断money_tcc表中是否有记录,如果有,一定是CANCEL执行过,要拒绝业务
        MoneyTcc moneyTccRecord = moneyTccMapper.selectById(xid);
        if (moneyTccRecord != null) {
            //CANCEL执行过, 要拒绝业务
            return;
        }

        //扣减可用余额
        //TODO 调用资金管理service扣除余额

        // 2.记录冻结金额,事务状态
        MoneyTcc moneyTcc = new MoneyTcc();
        moneyTcc.setUserId(userId);
        moneyTcc.setFrozenMoney(money);
        moneyTcc.setState(MoneyTcc.State.TRY);
        moneyTcc.setXid(xid);
        moneyTccMapper.insert(moneyTcc);
    }

    @Override
    public boolean confirm(BusinessActionContext ctx) {
        // 1.获取事务id
        String xid = ctx.getXid();
        // 2.根据id删除事务冻结记录表记录
        int count = moneyTccMapper.deleteById(xid);
        return count == 1;
    }

    @Override
    public boolean cancel(BusinessActionContext context) {
        // 0.查询冻结记录
        String xid = context.getXid();
        String userId = context.getActionContext("userId").toString();
        MoneyTcc moneyTcc = moneyTccMapper.selectById(xid);


        // 1.空回滚的判断,判断moneyTcc是否为null,为null证明try没执行,需要空回滚
        if (moneyTcc == null) {
            // 证明try没执行,需要空回滚
            //这里需要往money_tcc表添加数据,作为Try判断业务悬挂标志。
            MoneyTcc newMoneyTcc = new MoneyTcc();
            moneyTcc.setUserId(userId);
            moneyTcc.setFrozenMoney(0);
            moneyTcc.setState(MoneyTcc.State.CANCEL);
            moneyTcc.setXid(xid);
        }
        //2. 幂等判断
        if (moneyTcc.getState() == MoneyTcc.State.CANCEL) {
            // 已经处理过一次CANCEL了,无需重复处理
            return true;
        }

       // 恢复可用余额
       //TODO 调用资金管理service添加回在Try操作扣除的资金


        // 2.将冻结金额清零,状态改为CANCEL
        moneyTcc.setFrozenMoney(0);
        moneyTcc.setState(MoneyTcc.State.CANCEL);
        int count = moneyTccMapper.updateById(moneyTcc);
        return count == 1;
    }
}

 

6.SAGA模式

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

优势:

  • 一阶段提交本地事务,无锁,高性能
  • 事件驱动架构,参与者可异步执行,高吞吐
  • 补偿服务易于实现

缺点:

  • 软状态持续时间不确定(异步),时效性差
  • 没有锁,没有事务隔离,会有脏写

 

 

参考:

官网:Apache Seata

黑马程序员springcloud相关课程

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇