06分布式事务
什么是分布式事务
业务场景
下单业务中,前端请求首先进入订单服务,创建订单并写入数据库。然后订单服务调用购物车服务和库存服务:
- 购物车服务负责清理购物车信息
- 库存服务负责扣减商品库存

整个流程涉及订单服务、购物车服务、库存服务三个独立数据库。一旦其中某一步(如扣减库存时库存不足)失败,前面已成功执行的步骤(创建订单、清理购物车)无法回滚,导致订单已生成但库存未扣减的数据不一致问题,这就是典型的超卖场景。
分布式事务的定义
在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务,整个业务称为全局事务。

初识 Seata
Seata 简介
Seata 是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。

Seata 的学习路径包含:初识 Seata、部署 TC 服务、微服务集成 Seata、XA 模式、AT 模式。

分布式事务解决思路
解决分布式事务,各个子事务之间必须能感知到彼此的事务状态,才能保证状态一致。例如订单服务下单成功、购物车清理成功,但库存服务提示库存不足,需要让所有分支事务感知到这个失败状态并统一回滚。

Seata 架构
Seata 事务管理中有三个重要的角色:
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务
- RM (Resource Manager) - 资源管理器:管理分支事务,与 TC 交谈以注册分支事务和报告分支事务的状态
TM 开启并结束全局事务,各个微服务中的 RM 负责分支事务的注册与状态报告,TC 协调全局事务的提交或回滚。
部署 TC 服务
部署 TC 服务可参考讲义《2.2.部署TC服务》。

微服务集成 Seata
引入依赖
首先在项目中引入 Seata 依赖:
<!-- seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>配置 TC 服务地址
在 application.yml 中添加配置,让微服务找到 TC 服务地址:
seata:
registry: # 注册中心的配置,微服务根据这些信息去注册中心获取TC服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 192.168.150.101:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-server # seata服务名称
username: nacos
password: nacos
tx-service-group: hmall # 事务组名称
service:
vgroup-mapping: # 事务组与tc集群的映射关系
hmall: "default"为了避免重复配置,可以将 Seata 配置共享到 Nacos 中,由各个微服务统一拉取。
XA 模式
XA 规范
XA 规范是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,描述了全局的 TM 与局部的 RM 之间的接口,几乎所有主流的关系型数据库都对 XA 规范提供了支持。
XA 模式工作流程
Seata 的 XA 模式分为两个阶段:
一阶段的工作:
- RM 注册分支事务到 TC
- RM 执行分支业务 sql 但不提交
- RM 报告执行状态到 TC
二阶段的工作:
- TC 检测各分支事务执行状态
- 如果都成功,通知所有 RM 提交事务
- 如果有失败,通知所有 RM 回滚事务
- RM 接收 TC 指令,提交或回滚事务

XA 模式优缺点
优点:
- 事务的强一致性,满足 ACID 原则
- 常用数据库都支持,实现简单,并且没有代码侵入
缺点:
- 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务
实现 XA 模式
Seata 的 starter 已经完成了 XA 模式的自动装配,实现非常简单:
步骤一:修改 application.yml 文件(每个参与事务的微服务),开启 XA 模式:
seata:
data-source-proxy-mode: XA # 开启数据源代理的XA模式步骤二:给发起全局事务的入口方法添加 @GlobalTransactional 注解,本例中是 OrderServiceImpl 中的 create 方法:
@Override
@GlobalTransactional
public Long createOrder(OrderFormDTO order) {
// 创建订单 ... 略
// 清理购物车 ... 略
// 扣减库存 ...略
return order.getId();
}步骤三:重启服务并测试
AT 模式
AT 模式原理
Seata 主推的是 AT 模式,AT 模式同样是分阶段提交的事务模型,弥补了 XA 模型中资源锁定周期过长的缺陷。
阶段一 RM 的工作:
- 注册分支事务
- 记录 undo-log(数据快照)
- 执行业务 sql 并提交
- 报告事务状态
阶段二提交时 RM 的工作:
- 删除 undo-log 即可
阶段二回滚时 RM 的工作:
- 根据 undo-log 恢复数据到更新前
AT 模式与 XA 模式的区别
- XA 模式一阶段不提交事务,锁定资源;AT 模式一阶段直接提交,不锁定资源
- XA 模式依赖数据库机制实现回滚;AT 模式利用数据快照实现数据回滚
- XA 模式强一致;AT 模式最终一致
实现 AT 模式
步骤一:添加资料中的 seata-at.sql 到微服务对应的数据库中:
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT '分支事务id',
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务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';步骤二:修改 application.yml 文件,将事务模式修改为 AT 模式:
seata:
data-source-proxy-mode: AT # 开启数据源代理的AT模式