Spring Cloud Alibaba快速入门-分布式事务Seata(下)

文章目录


查看seata客户端中本次分布表事务的变化

回顾调用链

java 复制代码
@Override
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
    System.out.println("mark 1");
    //TODO 1. 扣减库存
    storageFeignClient.deduct(commodityCode, orderCount);
    System.out.println("mark 2");
    //TODO 2. 创建订单
    orderFeignClient.create(userId, commodityCode, orderCount);
}

发送请求观察

mark 1

查看全局锁

可以看到没有全局锁

mark 2

重新发送请求,有请求过期时间

查看数据库表undo_log

查看context字段数据

js 复制代码
{
  "@class": "org.apache.seata.rm.datasource.undo.BranchUndoLog",
  "xid": "192.168.109.1:8091:7161352577865949200",
  "branchId": 7161352577865949201,
  "sqlUndoLogs": [
    "java.util.ArrayList",
    [
      {
        "@class": "org.apache.seata.rm.datasource.undo.SQLUndoLog",
        "sqlType": "UPDATE",
        "tableName": "storage_tbl",
        "beforeImage": {
          "@class": "org.apache.seata.rm.datasource.sql.struct.TableRecords",
          "tableName": "storage_tbl",
          "rows": [
            "java.util.ArrayList",
            [
              {
                "@class": "org.apache.seata.rm.datasource.sql.struct.Row",
                "fields": [
                  "java.util.ArrayList",
                  [
                    {
                      "@class": "org.apache.seata.rm.datasource.sql.struct.Field",
                      "name": "count",
                      "keyType": "NULL",
                      "type": 4,
                      "value": 96
                    },
                    {
                      "@class": "org.apache.seata.rm.datasource.sql.struct.Field",
                      "name": "id",
                      "keyType": "PRIMARY_KEY",
                      "type": 4,
                      "value": 1
                    }
                  ]
                ]
              }
            ]
          ]
        },
        "afterImage": {
          "@class": "org.apache.seata.rm.datasource.sql.struct.TableRecords",
          "tableName": "storage_tbl",
          "rows": [
            "java.util.ArrayList",
            [
              {
                "@class": "org.apache.seata.rm.datasource.sql.struct.Row",
                "fields": [
                  "java.util.ArrayList",
                  [
                    {
                      "@class": "org.apache.seata.rm.datasource.sql.struct.Field",
                      "name": "count",
                      "keyType": "NULL",
                      "type": 4,
                      "value": 94
                    },
                    {
                      "@class": "org.apache.seata.rm.datasource.sql.struct.Field",
                      "name": "id",
                      "keyType": "PRIMARY_KEY",
                      "type": 4,
                      "value": 1
                    }
                  ]
                ]
              }
            ]
          ]
        }
      }
    ]
  ]
}

重点观察beforeImage和afterImage中的数据

注意:此时数据库表中的数据是已经修改好的数据。当二阶有异常时在修改回去

进入order服务

java 复制代码
@Override
public OrderTbl create(String userId, String commodityCode, int orderCount) {
    //1、计算订单价格
    int orderMoney = calculate(commodityCode, orderCount);

    //TODO 2、扣减账户余额
    accountFeignClient.debit(userId, orderCount);

    //3、保存订单
    OrderTbl orderTbl = new OrderTbl();
    orderTbl.setUserId(userId);
    orderTbl.setCommodityCode(commodityCode);
    orderTbl.setCount(orderCount);
    orderTbl.setMoney(orderMoney);

    System.out.println("mark 3");
    orderTblMapper.insert(orderTbl);

    //模拟异常
    int i = 10/0;

    return orderTbl;
}

mark 3

注意全局锁的pk是主键id

总结

一阶:本地事务提交(业务数据进行编辑+undo log表进行记录)

二阶:成功+失败

成功:所有人删除undo log表记录

失败:所有人拿到自己的前镜像(undo log表中rollback_info中有保留),恢复之前的数据,删除undo_log表记录

四种事务模式

官网:https://seata.apache.org/zh-cn/docs/dev/mode/at-mode

默认为AT模式,其中AT和TCC模式使用较多,XA模式效率较低,SAGA可以使用MQ代替

AT 模式(Automatic Transaction,自动事务)

  • AT 模式是 Seata 的默认模式,基于 "最终一致性" 设计,对业务无侵入(无需修改业务代码)
  • 最常用、对业务无侵入
  • 基于两阶段提交(2PC)改进
  • 第一阶段:执行业务 SQL,同时记录 undo log(回滚日志)
  • 第二阶段:
    • 提交:直接删除 undo log
    • 回滚:根据 undo log 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句

写隔离

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

以一个示例来说明:

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

  • tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。
  • tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。

    (如果tx1执行成功)
    tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。

    (如果tx1执行失败)
    如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
    此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
    因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

读隔离

在数据库本地事务隔离级别 读已提交 (Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

优缺点

优点:

  • 代码侵入性低,只需添加注解
  • 支持大多数SQL操作
  • 性能较高(一阶段提交,二阶段异步删除日志)

缺点:

  • 需要基于支持本地 ACID 事务的关系型数据库(undolog)
  • 全局锁可能影响并发

TCC 模式(Try-Confirm-Cancel)

TCC 是 "侵入式" 模式,需要开发者手动实现三个接口,适用于对一致性要求高、非 SQL 操作(比如调用第三方接口)的场景。

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

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

根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction Mode 和 TCC (Branch) Transaction Mode.

AT 模式基于 支持本地 ACID 事务 的 关系型数据库:

  • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
  • 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
  • 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

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

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
    所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

TCC 三个阶段

Try:资源检查和预留(比如冻结库存、冻结资金);

Confirm:确认执行业务(真正扣减库存、扣减资金),只有 Try 成功才会执行;

Cancel:取消预留资源(解冻库存、解冻资金),Try 失败时执行。

优缺点

优点:灵活(可处理非数据库操作)、一致性强、可跨异构系统

缺点:侵入性强(需手动写 Try/Confirm/Cancel 逻辑),开发复杂,需处理幂等、空回滚等问题

适用场景

  • 资金交易:需要强一致性的场景(支付场景等)
  • 需要自定义补偿逻辑:AT模式无法满足的场景

Saga 模式(长事务补偿)

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

SAGA 模式适用于长事务、业务流程可拆分的场景(比如订单履约、数据审批等),基于 "补偿" 思想实现最终一致性。

核心逻辑

  • 将长事务拆分为多个短事务(Step),每个短事务执行成功后提交;
  • 如果某个 Step 失败,执行前面所有 Step 的补偿操作(比如创建订单成功→扣库存失败→执行 "取消订单" 补偿);
  • 支持正向 / 反向流程编排,可配置重试策略。

Saga的实现

基于状态机引擎的 Saga 实现:

目前SEATA提供的Saga模式是基于状态机引擎来实现的,机制是:

  1. 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件
  2. 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点
  3. 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚

注意: 异常发生时是否进行补偿也可由用户自定义决定

  1. 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能

示例状态图:

XA 模式(强一致性)

什么是 XA?

  • XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
  • XA 规范 描述了全局的事务管理器与局部的资源管理器之间的接口。XA规范的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。
  • XA 规范 使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。
  • XA 规范 在上世纪 90 年代初就被提出。目前,几乎所有主流的数据库都对 XA 规范 提供了支持。

什么是 Seata 的 XA 模式?

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

执行阶段:

可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚

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

完成阶段:

分支提交:执行 XA 分支的 commit

分支回滚:执行 XA 分支的 rollback

为什么支持 XA ?

为什么要在 Seata 中增加 XA 模式呢?支持 XA 的意义在哪里呢?

1、补偿型事务模式的问题

本质上,Seata 已经支持的 3 大事务模式:AT、TCC、Saga 都是 补偿型 的。

补偿型 事务处理机制构建在 事务资源 之上(要么在中间件层面,要么在应用层面),事务资源 本身对分布式事务是无感知的。

事务资源 对分布式事务的无感知存在一个根本性的问题:无法做到真正的 全局一致性 。

比如,一条库存记录,处在 补偿型 事务处理过程中,由 100 扣减为 50。此时,仓库管理员连接数据库,查询统计库存,就看到当前的 50。之后,事务因为异外回滚,库存会被补偿回滚为 100。显然,仓库管理员查询统计到的 50 就是 脏 数据。

可以看到,补偿型 分布式事务机制因为不要求 事务资源 本身(如数据库)的机制参与,所以无法保证从事务框架之外的全局视角的数据一致性。

2、XA 的价值

与 补偿型 不同,XA 协议 要求 事务资源 本身提供对规范和协议的支持。

因为 事务资源 感知并参与分布式事务处理过程,所以 事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。

除了 全局一致性 这个根本性的价值外,支持 XA 还有如下几个方面的好处:

1、业务无侵入:和 AT 一样,XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担。

2、数据库的支持广泛:XA 协议被主流关系型数据库广泛支持,不需要额外的适配即可使用。

3、多语言支持容易:因为不涉及 SQL 解析,XA 模式对 Seata 的 RM 的要求比较少,为不同语言开发 SDK 较之 AT 模式将更 薄,更容易。

4、传统基于 XA 应用的迁移:传统的,基于 XA 协议的应用,迁移到 Seata 平台,使用 XA 模式将更平滑。

整体机制

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

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

运行机制

XA 模式 运行在 Seata 定义的事务框架内:

  • 执行阶段(E xecute):
    • XA start/XA end/XA prepare + SQL + 注册分支
  • 完成阶段(F inish):
    • XA commit/XA rollback

相关文章:
Spring Cloud Alibaba快速入门
Spring Cloud Alibaba快速入门-Nacos注册中心(上)
Spring Cloud Alibaba快速入门-Nacos注册中心(下)
Spring Cloud Alibaba快速入门-Nacos配置中心(上)
Spring Cloud Alibaba快速入门-Nacos配置中心(下)
Spring Cloud Alibaba快速入门-OpenFeign
Spring Cloud Alibaba快速入门-OpenFeign进阶用法
Spring Cloud Alibaba快速入门-Sentinel
Spring Cloud Alibaba快速入门-Sentinel流量控制(FlowRule)
Spring Cloud Alibaba快速入门-Sentinel熔断规则
Spring Cloud Alibaba快速入门-Sentinel热点参数限流
Spring Cloud Alibaba快速入门-Gateway
Spring Cloud Alibaba快速入门-分布式事务Seata
Spring Cloud Alibaba快速入门-分布式事务Seata(下)

Spring Cloud Netflix快速入门

相关推荐
机器觉醒时代1 天前
定义下一代机器人训练?智元 SOP:VLA 模型真实世界分布式在线后训练的关键突破
分布式·机器人·ai大模型·人形机器人
skywalk81631 天前
FreeBSD系统使用docker-compose使用docker容器(没搞定)
spring cloud·docker·容器
Honmaple1 天前
SpringBoot + Seata + Nacos:分布式事务落地实战,订单-库存一致性全解析
spring boot·分布式·后端
阳宗德1 天前
分布式解决方案:从痛点拆解到思路落地
分布式
2501_941805931 天前
面向高可用微服务体系的状态管理演进与多语言实现经验融合实践分享文章
java·大数据·分布式
rchmin1 天前
分布式ID生成方法详解
分布式
笃行客从不躺平1 天前
分布式事务核心原理与主流模型学习
分布式
Coder_Boy_1 天前
基于SpringAI的在线考试系统设计-用户管理模块设计
java·大数据·人工智能·spring boot·spring cloud
小北方城市网1 天前
第 5 课:服务网格(Istio)实战|大规模微服务的流量与安全治理体系
大数据·开发语言·人工智能·python·安全·微服务·istio