Seata基础使用-分布式事务

零、本文纲要

  • 一、事务
  • 二、分布式事务
  • 三、Seata基础
    1、认识Seata
    2、部署TC(Server端)
    3、微服务集成Seata
  • 四、Seata事务管理-XA模式
    1、XA模式
    2、XA模式特点
    3、实现XA模式
  • 五、Seata事务管理-AT模式
    1、AT模式
    2、AT模式预防脏写
    3、AT模式特点
    4、AT模式实现
  • 六、Seata事务管理-TCC模式
    1、TCC模式
    2、TCC模式特点
    3、TCC模式注意点
    4、TCC模式实现
  • 七、Seata事务管理-Saga模式
    1、Saga模式
    2、Saga模式特点
    3、Saga模式实现
  • 八、Seata事务管理对比

一、事务

1、事务介绍

事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作的请求,即这些操作要么同时成功,要么同时失败。

2、事务特性

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败;
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态;
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行;
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

二、分布式事务

1、分布式服务案例

  • ① 微服务下单业务

Ⅰ 下单业务调用订单服务

创建订单、写入数据库;

Ⅱ 订单服务调用账户服务库存服务

账户服务负责扣减用户余额;

库存服务负责扣减商品库存。

微服务下单业务.png
  • ② 存在的问题

各个子事务的一致性问题:
各个事务是非关联的,独立部署在各个机器(或者同一机器不同JVM环境中),一个事务回滚,不影响其他事务正常提交。

2、CAP定理

  • Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致;
  • Availability (可用性):用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝;
  • Partition tolerance(分区容错)
    Partition(分区):因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区;
    Tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务。

3、BASE理论

  • Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
  • Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
  • Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。

4、分布式事务处理

  • ① 分布式事务模型

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

分布式事务处理.png

子系统事务称为分支事务,有关联的各个分支事务在一起称为全局事务

  • ② 基于理论处理分布式事务

Ⅰ AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致
Ⅱ CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。

三、Seata基础

1、认识Seata

官方网址:Seata

  • ① Seata事务管理
Seata事务管理.png

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

  • ② Seata分布式事务解决方案

XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入;
TCC模式:最终一致的分阶段事务模式,有业务侵入;
AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
SAGA模式:长事务模式,有业务侵入。

2、部署TC(Server端)

  • ① 下载

官方下载:下载中心 (seata.io)

  • ② 解压
Server端.png
  • ③ 修改配置
image.png

此处我们将Seata服务注册到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 = "HZ"
    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"
  }
}
  • ④ 添加seataServer.properties配置文件
seataServer.properties配置文件.png

记得按需修改数据库相关配置:
Ⅰ 驱动:com.mysql.cj.jdbc.Driver
Ⅱ 时区:&serverTimezone=UTC
Ⅲ 账户&密码。

# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

# 事务、日志等配置
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.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000

# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
  • ⑤ 创建数据库表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- 分支事务表
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table`  (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` tinyint(4) NULL DEFAULT NULL,
  `client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime(6) NULL DEFAULT NULL,
  `gmt_modified` datetime(6) NULL DEFAULT NULL,
  PRIMARY KEY (`branch_id`) USING BTREE,
  INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- 全局事务表
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table`  (
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `timeout` int(11) NULL DEFAULT NULL,
  `begin_time` bigint(20) NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`xid`) USING BTREE,
  INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
  INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;
  • ⑥ 启动TC服务
启动TC服务.png

Win指令:start seata-server.bat

3、微服务集成Seata

  • ① 引入依赖

按需改成自己下载版本依赖即可,此处以1.4.2版本为例:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!--版本较低,1.3.0,因此排除-->
        <exclusion>
            <artifactId>seata-spring-boot-starter</artifactId>
            <groupId>io.seata</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!--seata starter 采用1.4.2版本-->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>${seata.version}</version>
</dependency>

*② 修改配置文件application.yml

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: HZ
      username: nacos
      password: nacos
  tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
  service:
    vgroup-mapping: # 事务组与TC服务cluster的映射关系
      seata-demo: HZ

让微服务通过注册中心找到seata-tc-server(namespace + group + serviceName + cluster),需要配置事务组的信息,如下:

事务组的说明.png

四、Seata事务管理-XA模式

1、XA模式

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。

Seata的XA模式.png
  • ① 一阶段

RM一阶段的工作:
Ⅰ 注册分支事务到TC;
Ⅱ 执行分支业务sql但不提交;
Ⅲ 报告执行状态到TC。

  • ② 二阶段

TC二阶段的工作:
Ⅰ TC检测各分支事务执行状态;
如果都成功,通知所有RM提交事务;
如果有失败,通知所有RM回滚事务。

RM二阶段的工作:
Ⅰ 接收TC指令,提交或回滚事务。

2、XA模式特点

  • ① 优点

事务的强一致性,满足ACID原则;
常用数据库都支持,实现简单,并且没有代码侵入

  • ② 缺点

因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
依赖关系型数据库实现事务。

3、实现XA模式

  • ① 修改application.yml配置文件
seata:
  data-source-proxy-mode: XA # 开启数据源代理的XA模式
  • ② 给发起全局事务的入口方法添加@GlobalTransactional注解
    @Override
    //@Transactional
    @GlobalTransactional
    public Long create(Order order) {
        // 创建订单
        orderMapper.insert(order);
        try {
            // 扣用户余额
            accountClient.deduct(order.getUserId(), order.getMoney());
            // 扣库存
            storageClient.deduct(order.getCommodityCode(), order.getCount());

        } catch (FeignException e) {
            log.error("下单失败,原因:{}", e.contentUTF8(), e);
            throw new RuntimeException(e.contentUTF8(), e);
        }
        return order.getId();
    }
  • ③ 测试

五、Seata事务管理-AT模式

1、AT模式

AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。

AT模式.png
  • ① 阶段一

阶段一RM的工作:
Ⅰ 注册分支事务;
Ⅱ 记录undo-log(数据快照)
Ⅲ 执行业务sql并提交
Ⅳ 报告事务状态。

  • ② 阶段二

阶段二提交时RM的工作:
Ⅰ 删除undo-log即可;

阶段二回滚时RM的工作:
Ⅰ 根据undo-log恢复数据到更新前。

2、AT模式预防脏写

  • ① 全局锁写隔离

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

全局锁预防脏写.png
  • ② 全局锁锁表

关键记录:xid事务idtable_name表名pk锁的行id等,该表给TC服务使用。
注意:仅受Seata管理的事务才会被全局锁管理。

-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table`  (
  `row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`row_key`) USING BTREE,
  INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
  • ③ undo_log表

特殊的:非Seata管理的事务修改全局锁管理事务的数据,回滚时产生数据异常。
Seata处理方式:
插入回滚日志:把前后镜像(afterImage、beforeImage)数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。
数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。

该表给微服务使用。

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci 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 INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;

-- ----------------------------
-- Records of undo_log
-- ----------------------------

3、AT模式特点

  • ① 优点

Ⅰ 一阶段完成直接提交事务,释放数据库资源,性能比较好;
Ⅱ 利用全局锁实现读写隔离;
Ⅲ 没有代码侵入,框架自动完成回滚和提交。

  • ② 缺点

Ⅰ 两阶段之间属于软状态,属于最终一致;
Ⅱ 框架的快照功能会影响性能,但比XA模式要好很多。

4、AT模式实现

  • ① 准备TC全局锁锁表

导入lock_table,详见上方。

  • ② 准备微服务undo_log表

导入undo_log,详见上方。

  • ③ 修改application.yml配置文件
seata:
  data-source-proxy-mode: AT # 开启数据源代理的AT模式

六、Seata事务管理-TCC模式

1、TCC模式

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。

把 自定义 的分支事务纳入到全局事务的管理中,TCC模式需要实现三个方法:

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

2、TCC模式特点

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

  • ② 缺点
    Ⅰ 有代码侵入,需要人为编写Try、Confirm和Cancel接口,太麻烦;
    Ⅱ 软状态,事务是最终一致;
    Ⅲ 需要考虑Confirm和Cancel的失败情况,做好幂等处理。

3、TCC模式注意点

  • ① 保证confirm、cancel接口的幂等性
  • ② 允许空回滚
  • ③ 拒绝业务悬挂

空回滚:Try阶段阻塞,Cancel执行。
业务悬挂:Try阶段阻塞,Cancel执行,Try恢复后又执行,后续无Confirm/Cancel。

4、TCC模式实现

  • ① 接口准备

Ⅰ 接口上使用@LocalTCC注解
Ⅱ Try方法上使用@TwoPhaseBusinessAction注解,配置好Try、Confirm、Cancel方法;
Ⅲ 需要传递的参数使用@BusinessActionContextParameter注解配置;
Ⅳ Confirm、Cancel方法需要boolean返回值。

@LocalTCC
public interface AccountTCCService {

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

    public boolean confirm(BusinessActionContext context);

    public boolean cancel(BusinessActionContext context);
}
  • ② 编写实现类
@Slf4j
@Service
public class AccountTCCServiceImpl implements AccountTCCService{

    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper accountFreezeMapper;

    ... ...

}
  • ③ 表格准备

Ⅰ account_tbl表

CREATE TABLE `account_tbl` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `money` int unsigned DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT;

Ⅱ account_freeze_tbl表

xid事务id、state事务状态、冻结资源、对应账户。

CREATE TABLE `account_freeze_tbl` (
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `freeze_money` int unsigned DEFAULT '0',
  `state` int DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
  PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT;
  • ④ 编写Try方法

Ⅰ 事务ID获取方法:String xid = RootContext.getXID();
Ⅱ 防止悬挂操作:

        // 【悬挂操作】判断freeze中是否有冻结记录,如果有,一定是CANCEL执行过,需要拒绝业务
        AccountFreeze originFreeze = accountFreezeMapper.selectById(xid);
        if (originFreeze != null){
            // CANCEL执行过,需要拒绝业务
            return;
        }

完整方法体如下:

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

        // 【悬挂操作】判断freeze中是否有冻结记录,如果有,一定是CANCEL执行过,需要拒绝业务
        AccountFreeze originFreeze = accountFreezeMapper.selectById(xid);
        if (originFreeze != null){
            // CANCEL执行过,需要拒绝业务
            return;
        }

        // 1. 扣减可用余额
        accountMapper.deduct(userId, money);
        // 2. 记录冻结金额,事务状态
        // 2.1 设置冻结金额信息
        AccountFreeze freeze = new AccountFreeze();
        freeze.setUserId(userId);
        freeze.setFreezeMoney(money);
        freeze.setState(AccountFreeze.State.TRY);
        freeze.setXid(xid);
        // 2.2 将信息写入数据库
        accountFreezeMapper.insert(freeze);
    }
  • ⑤ 编写Confirm方法
    @Override
    public boolean confirm(BusinessActionContext context) {
        // 1. 获取事务id
        String xid = context.getXid();
        // 2. 根据事务id删除冻结金额
        int count = accountFreezeMapper.deleteById(xid);
        return count == 1;
    }
  • ⑥ 编写Cancel方法

Ⅰ 空回滚:

        // 【空回滚】的判断,判断freeze对象是否为null,为null证明try没有执行,需要空回滚
        if (freeze == null){
            freeze = new AccountFreeze();
            freeze.setUserId(userId);
            freeze.setFreezeMoney(0);
            freeze.setState(AccountFreeze.State.CANCEL);
            freeze.setXid(xid);
            accountFreezeMapper.insert(freeze);
            return true;
        }

Ⅱ 幂等:

        // 【幂等】判断
        if (freeze.getState() == AccountFreeze.State.CANCEL){
            // 已经处理过一次CANCEL了,无需重复处理
            return true;
        }

完整方法体如下:

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

        // 【空回滚】的判断,判断freeze对象是否为null,为null证明try没有执行,需要空回滚
        if (freeze == null){
            freeze = new AccountFreeze();
            freeze.setUserId(userId);
            freeze.setFreezeMoney(0);
            freeze.setState(AccountFreeze.State.CANCEL);
            freeze.setXid(xid);
            accountFreezeMapper.insert(freeze);
            return true;
        }

        // 【幂等】判断
        if (freeze.getState() == AccountFreeze.State.CANCEL){
            // 已经处理过一次CANCEL了,无需重复处理
            return true;
        }

        // 1. 恢复可用金额
        accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());

        // 2. 将冻结金额清零,状态改为CANCEL
        freeze.setFreezeMoney(0);
        freeze.setState(AccountFreeze.State.CANCEL);
        int count = accountFreezeMapper.updateById(freeze);
        return count == 1;
    }

七、Seata事务管理-Saga模式

1、Saga模式

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

Saga模式.png

2、Saga模式特点

  • ① 优点

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

  • ② 缺点

Ⅰ 不保证隔离性。

3、Saga模式实现

官方demo:GitHub - seata/seata-samples: seata-samples

可以参考官方介绍:Seata Saga 模式

八、Seata事务管理对比

模式 XA AT TCC SAGA
一致性 强一致 弱一致 弱一致 最终一致
隔离性 完全隔离 基于全局锁隔离 基于资源预留隔离 无隔离
代码侵入 有,需要编写三个接口方法 有,需要编写状态机和补偿业务
性能 非常好 非常好
场景 对一致性、隔离性有高要求的业务 基于关系型数据库的大多数分布式事务场景都可以 对性能要求较高的事务。有非关系型数据库要参与的事务。 业务流程长、业务流程多。参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口。

九、结尾

以上即为Seata基础使用-分布式事务的全部内容,感谢阅读。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容