06分布式事务

1.什么是分布式事务

1.1当前项目的事务不一致性问题

我们先看一下当前项目存在的问题:
在下单过程中,会先调用交易服务创建订单,然后调用购物车服务清理购物车,最后调用库存服务减少库存:

场景​​:

A 商品的库存为 1,同时有 ​​a、b 两个线程​​ 调用交易服务:

  • ​a 线程​​ 成功执行:​​创建订单 → 清理购物车 → 扣减库存(库存变为 0)​
  • ​b 线程​​ 执行:​​创建订单 → 清理购物车​​,但在 ​​扣减库存​​ 时发现库存已为 0,导致失败。

​问题​​:

b 线程已经执行了 ​​创建订单​​ 和 ​​清理购物车​​,但由于库存不足,无法完成交易。然而,这些操作已经生效,无法回滚,导致数据不一致(订单已创建但库存未扣减)。

你可能想到了使用@Transactional解决事务不一致的问题,但是@Transactional不行

为什么 @Transactional无法解决?​

在 ​​单体架构​​ 中,我们可以用 @Transactional保证事务:

@Transactional
public void purchase() {
createOrder(); // 操作本地数据库
clearCart(); // 操作本地数据库
deductStock(); // 操作本地数据库
// 任意一步失败,整个事务回滚
}

但拆分微服务后​​:

  • createOrder()属于 ​​订单服务​​(可能用 MySQL)
  • clearCart()属于 ​​购物车服务​​(可能用 Redis)
  • deductStock()属于 ​​库存服务​​(可能用另一个数据库)

@Transactional的局限性​​:

  1. ​只能管理单个数据库的事务​​(如 MySQL 事务),无法跨服务协调。
  2. ​不同存储引擎​​(如 MySQL + Redis)​​无法共用事务​​。
  3. ​远程调用(RPC/HTTP)不在事务范围内​​,失败后无法自动回滚。

1.2什么是分布式事务

分布式事务就是为了解决上面这种A服务的a方法调用了B服务的b方法,为了保证事务一致性,我们需要将a方法、b方法作为一整个事务,如果其中一个方法失败,整个事务一起回滚。

2.认识Seata

在众多分布式事务解决方案中,阿里巴巴于2019年开源的 ​​Seata​​ 因其功能完善、使用广泛,已成为最主流的开源框架。

分布式事务的核心问题是多个分支事务之间无法感知彼此的执行状态。解决方案的核心思想是引入一个​​统一的事务协调者​​,通过与各分支事务通信来监控执行状态,确保所有分支事务要么全部成功,要么全部失败。主流的分布式事务框架都基于这一理论实现。

Seata 也采用这种架构,包含三个核心角色:

  • ​TC(事务协调者)​​:协调全局事务,维护事务状态,决策提交或回滚
  • ​TM(事务管理器)​​:定义全局事务边界,控制事务的开启和结束
  • ​RM(资源管理器)​​:管理分支事务,向TC注册并汇报状态,执行具体的事务操作

3.部署TC服务

Seata支持多种存储模式,但考虑到持久化的需要,我们一般选择基于数据库存储。运行资料day05的seata-tc.sql

将资料day05的整个seata文件夹拷贝到虚拟机的/root目录:

在虚拟机的/root目录执行下面的命令:

docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.150.101 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2

4.微服务集成Seata

以trade-service模块为例,引入依赖

<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>

在nacos上添加一个共享的seata配置,命名为shared-seata.yaml:

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: "default"

改造trade-service模块,添加bootstrap.yaml:

spring:
  application:
    name: trade-service # 服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.150.101 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置
          - dataId: shared-seata.yaml # 共享seata配置

然后改造application.yaml文件,内容如下:

server:
  port: 8085
feign:
  okhttp:
    enabled: true # 开启OKHttp连接池支持
  sentinel:
    enabled: true # 开启Feign对Sentinel的整合
hm:
  swagger:
    title: 交易服务接口文档
    package: com.hmall.trade.controller
  db:
    database: hm-trade

seata的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。因此我们要先准备一个这样的表。
因为交易过程涉及hm-trade、hm-cart、hm-item三个模块,运行day05资料的seata-at.sql分别文件导入hm-trade、hm-cart、hm-item三个数据库中。结果:

按照上面流程,对应地改造hm-item和hm-cart模块

找到trade-service模块下的com.hmall.trade.service.impl.OrderServiceImpl类中的createOrder方法,也就是下单业务方法。将其上的@Transactional注解改为Seata提供的@GlobalTransactional:

@GlobalTransactional注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。现在重启三个项目即可。

现在可以测试了,先在前端购物车中选中几个需要购买的商品,然后点击结算。

然后不要立即提交订单,先去数据库把其中至少一个商品的库存调整成0。

然后再回去点击“提交订单”,会报错,但是刷新后可以看到购物车数据并没有被清除,证明“创建订单,清除购物车,扣减库存”确实是一整个事务,其中一个事件失败后整个事务都会回滚。

5.XA模式和AT模式

Seata支持四种不同的分布式事务解决方案:

  • XA
  • TCC
  • AT
  • SAGA

常用的是XA和AT
XA模式:

工作流程(两阶段提交):​

  1. ​第一阶段 – 准备(Prepare)​​:
    • 事务协调者(TC)向所有参与者(RM)发送“准备”命令。
    • 各参与者执行本地事务(但不提交),将执行成功或失败的结果返回给 TC。
  2. ​第二阶段 – 提交/回滚(Commit/Rollback)​​:
    • ​如果所有参与者都准备成功​​:TC 向所有参与者发送“提交”命令,各参与者正式提交本地事务。
    • ​如果任何一个参与者准备失败​​:TC 向所有参与者发送“回滚”命令,各参与者回滚自己的本地事务。

AT模式:

工作流程:​

  1. ​第一阶段​​:
    • ​解析 SQL​​:RM 拦截业务 SQL,解析其语义(如要更新某张表的某个数据)。
    • ​生成快照​​:查询数据更新前的“前置镜像”(Before Image),并保存到 undo_log表中。
    • ​执行并提交本地事务​​:​​直接执行业务 SQL 并提交本地事务​​,释放本地锁。
    • ​生成后置镜像​​:记录数据更新后的“后置镜像”(After Image)。
    • ​报告状态​​:向 TC 注册分支事务并报告成功状态。
  2. ​第二阶段​​:
    • ​如果全部成功​​:TC 异步地快速删除各个 RM 中的 undo_log日志,流程结束。
    • ​如果需要回滚​​:
      • TC 通知各 RM 进行回滚。
      • RM 根据 undo_log表中的前置镜像,生成一条​​回滚 SQL​​(例如,如果之前是 update,就生成一个反向的 update)。
      • ​回滚前校验脏写​​:将当前数据与后置镜像对比,如果不一致,说明数据被其他事务修改(脏写),需要人工介入。
      • 执行回滚 SQL,将数据恢复原状,然后删除 undo_log记录。
特性​XA 模式​​AT 模式 (默认)​
​怎么做的?​​所有操作先预备着,但不真提交​​。等协调者说“可以了”,大家再一起提交。​每个操作直接自己先提交​​,但同时拍个快照(存到undo_log表)。万一要回滚,就按快照恢复。
​性能​​差​​(因为大家要一直等着,资源被锁住)​好​​(因为不用等,办完自己的事就行了)
​一致性​​强一致性​​(像排队一样,绝对整齐划一)​最终一致性​​(几乎实时一致,但理论上有个微小延迟)
​好比​​团体操​​:所有人摆好姿势不动,听口令一起做完。​各自跑步​​:每个人先跑到终点,但裁判用相机记录了过程,谁犯规了再叫回来重跑。

seata默认是AT模式,我们可以在Nacos中的共享shared-seata.yaml配置文件中设置:

seata:
  data-source-proxy-mode: XA

项目下载地址

gupengzu/high-concurrency-project at 06Distributed-Transaction

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注