跳到主要内容

Spring Cloud 分布式事务完全指南

"在分布式系统中,数据一致性是核心挑战,掌握分布式事务是微服务架构的必备技能"

📚 目录


分布式事务基础

什么是分布式事务?

分布式事务是指涉及多个独立数据库或服务的事务操作,这些操作需要要么全部成功,要么全部失败,保持数据的一致性。

为什么需要分布式事务?

在单体应用中,我们使用本地事务(@Transactional)就能保证数据一致性:

// ❌ 单体应用中的本地事务 - 在微服务中失效
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
// 1. 保存订单
orderMapper.insert(order);
// 2. 扣减库存
inventoryMapper.deduct(order.getProductId(), order.getCount());
// 3. 扣减余额
accountMapper.deduct(order.getUserId(), order.getAmount());
// 本地事务可以保证这三个操作要么全成功,要么全失败
}
}

但在微服务架构中,这些操作分散在不同的服务中:

// ✅ 微服务架构中需要分布式事务
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService; // 库存服务
@Autowired
private AccountService accountService; // 账户服务

@GlobalTransactional // Seata分布式事务注解
public void placeOrder(Order order) {
// 1. 保存订单
orderMapper.insert(order);
// 2. 调用库存服务扣减库存 (远程调用)
inventoryService.deduct(order.getProductId(), order.getCount());
// 3. 调用账户服务扣减余额 (远程调用)
accountService.deduct(order.getUserId(), order.getAmount());
// 需要分布式事务保证跨服务的操作一致性
}
}

分布式事务的核心挑战

分布式事务面临的挑战:
├── 网络不确定性
│ ├── 请求可能丢失
│ ├── 响应可能延迟
│ └── 服务可能不可用
├── 数据隔离
│ ├── 不同数据库之间无法直接加锁
│ ├── 跨服务的并发控制困难
│ └── 死锁问题复杂化
├── 性能与一致性权衡
│ ├── 强一致性严重影响性能
│ ├── 最终一致性设计复杂
│ └── 补偿机制难以完善
└── 故障恢复
├── 服务宕机后的状态恢复
├── 数据回滚的复杂性
└── 重试机制的幂等性保证

理论基础

CAP定理

CAP定理指出,一个分布式系统不可能同时满足以下三点:

特性说明示例
C - Consistency
一致性
所有节点在同一时间看到相同的数据写入后,所有读取都返回最新值
A - Availability
可用性
每个请求都能得到响应(不保证是最新数据)服务始终能响应,不因故障而拒绝
P - Partition Tolerance
分区容错性
系统在网络分区时仍能继续运行节点间网络断开时,系统仍能运行

核心结论: 在分布式系统中,网络分区(P)是必然存在的,因此只能在CA之间做权衡:

CAP权衡选择:
├── CP - 保证一致性和分区容错
│ ├── 代表: Seata的XA模式、Zookeeper
│ ├── 特点: 强一致性,但可能拒绝服务
│ └── 场景: 金融系统、支付系统

├── AP - 保证可用性和分区容错
│ ├── 代表: Seata的TCC/SAGA模式、Cassandra
│ ├── 特点: 高可用,但数据可能短期不一致
│ └── 场景: 社交媒体、内容分发

└── CA - 保证一致性和可用性
├── 代表: 传统单机数据库(RDBMS)
├── 特点: 无法应对网络分区
└── 场景: 单体应用

BASE理论

BASE理论是对CAP理论的补充,提倡采用最终一致性:

理论含义实现方式
BA - Basically Available
基本可用
允许损失部分可用性响应时间增加、功能降级
S - Soft State
软状态
允许数据存在中间状态数据同步过程中存在延迟
E - Eventually Consistent
最终一致性
数据最终会达到一致状态通过补偿机制、重试等

BASE vs ACID:

// ACID: 强一致性,刚性事务
@Transactional(isolation = Isolation.SERIALIZABLE)
public void traditionalTransaction() {
// 所有操作必须同时成功或失败
// 任何一步失败都会完全回滚
// 性能较差,但数据绝对一致
}

// BASE: 最终一致性,柔性事务
@GlobalTransactional
public void flexibleTransaction() {
// 允许中间状态的存在
// 通过补偿机制保证最终一致
// 性能较好,但实现复杂
}

分布式事务一致性模型

一致性模型层次:
强一致性 ◄─────────────────────────────────────► 弱一致性
│ │
├─ 顺序一致性 ├─ 因果一致性
│ │
├─ 因果一致性 └─ 最终一致性

└─ 最终一致性

Spring Cloud中常用的一致性级别:

  1. 强一致性 (CP): XA模式
  2. 最终一致性 (AP): TCC、SAGA、AT模式

分布式事务解决方案

方案对比总览

分布式事务方案全景:
├── 2PC/3PC 两阶段提交
│ ├── 优点: 强一致性,实现简单
│ ├── 缺点: 性能差,存在阻塞,单点故障
│ └── 代表: XA协议、Seata XA模式

├── TCC (Try-Confirm-Cancel)
│ ├── 优点: 性能好,无锁,灵活
│ ├── 缺点: 代码侵入性强,开发成本高
│ └── 代表: Seata TCC模式、Hmily

├── SAGA (长事务解决方案)
│ ├── 优点: 适合长事务,可观测性好
│ ├── 缺点: 状态机复杂,开发成本高
│ └── 代表: Seata SAGA模式、ServiceComb

├── 本地消息表
│ ├── 优点: 实现简单,可靠性高
│ ├── 缺点: 需要定时任务扫表
│ └── 代表: RocketMQ事务消息

└── AT (自动事务模式)
├── 优点: 无侵入,使用简单
├── 缺点: 需要解析SQL,存在锁竞争
└── 代表: Seata AT模式

详细对比表

方案一致性性能复杂度代码侵入适用场景
XA (2PC)强一致⭐⭐金融交易、支付系统
AT最终一致⭐⭐⭐⭐⭐⭐一般业务、电商订单
TCC最终一致⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐高并发、核心业务
SAGA最终一致⭐⭐⭐⭐⭐⭐⭐⭐长流程、业务链长
本地消息表最终一致⭐⭐⭐⭐⭐⭐异步场景、解耦需求

Seata框架深度解析

Seata是什么?

Seata (Simple Extensible Autonomous Transaction Architecture) 是阿里开源的一站式分布式事务解决方案,提供了AT、TCC、SAGA、XA四种事务模式。

Seata架构组件:
├── TC (Transaction Coordinator) - 事务协调器
│ ├── 维护全局事务的状态
│ ├── 驱动全局提交或回滚
│ └── 是一个独立部署的服务

├── TM (Transaction Manager) - 事务管理器
│ ├── 定义全局事务的范围
│ ├── 发起全局事务
│ ├── 提交或回滚全局事务
│ └── 由应用程序中发起事务的方担任

└── RM (Resource Manager) - 资源管理器
├── 管理分支事务的资源
├── 向TC注册分支事务
├── 汇报分支事务的状态
├── 驱动分支事务的提交或回滚
└── 每个参与事务的数据库都是RM

Seata事务流程

// 典型的Seata分布式事务流程
@GlobalTransactional // TM: 开启全局事务
public void purchaseOrder() {
// 1. TM向TC申请开启全局事务,TC生成XID
String xid = RootContext.getXID();

// 2. RM1: 订单服务 - 创建订单
orderService.createOrder(order);
// 订单数据库作为RM,向TC注册分支事务

// 3. RM2: 库存服务 - 扣减库存
inventoryService.deductStock(productId, count);
// 库存数据库作为RM,向TC注册分支事务

// 4. RM3: 账户服务 - 扣减余额
accountService.deductBalance(userId, amount);
// 账户数据库作为RM,向TC注册分支事务

// 5. TM: 如果所有分支事务都成功,TC通知所有RM提交
// 如果有任何一个失败,TC通知所有RM回滚
}

Seata四种模式对比

Seata模式选择指南:
├── AT模式 (推荐入门)
│ ├── 特点: 无侵入,自动补偿
│ ├── 原理: 自动解析SQL,记录undo_log
│ ├── 性能: 中等
│ └── 场景: 普通业务,性能要求不高

├── TCC模式 (高性能场景)
│ ├── 特点: 需要编写三个接口
│ ├── 原理: 应用层自定义补偿逻辑
│ ├── 性能: 高
│ └── 场景: 核心业务,高并发要求

├── SAGA模式 (长事务场景)
│ ├── 特点: 状态机定义业务流程
│ ├── 原理: 编排+补偿
│ ├── 性能: 中高
│ └── 场景: 业务流程长、服务多

└── XA模式 (强一致性场景)
├── 特点: 数据库XA协议支持
├── 原理: 两阶段提交(2PC)
├── 性能: 低
└── 场景: 金融交易、强一致性要求

Seata AT模式实战

AT模式原理

AT模式 (Automatic Transaction) 是Seata默认的模式,对业务代码零侵入,通过自动解析SQL实现事务控制。

AT模式两阶段提交:
├── 第一阶段: 业务数据和回滚日志记录在同一个本地事务中提交
│ ├── 1. 解析SQL语义,找到要修改的数据(before image)
│ ├── 2. 执行业务SQL
│ ├── 3. 查询修改后的数据(after image)
│ ├── 4. 保存undo_log(包含before和after镜像)
│ └── 5. 提交本地事务,释放本地锁

└── 第二阶段: 异步提交或回滚
├── 提交场景:
│ └── 异步删除undo_log,释放全局锁

└── 回滚场景:
├── 读取undo_log
├── 生成反向SQL
├── 执行回滚操作
└── 删除undo_log

快速开始

1. 安装Seata Server

# 下载Seata Server
wget https://github.com/seata/seata/releases/download/v2.0.0/seata-server-2.0.0.zip

# 解压
unzip seata-server-2.0.0.zip

# 修改配置文件 conf/file.conf
# 修改配置文件 conf/registry.conf

# 启动Seata Server (默认端口8091)
cd seata/bin
sh seata-server.sh -p 8091

2. 添加依赖

<!-- pom.xml -->
<dependencies>
<!-- Seata Spring Boot Starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>

<!-- 需要使用分布式事务的微服务都要添加 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
</dependencies>

3. 配置文件

# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: my_test_tx_group # 事务组名
service:
vgroup-mapping:
my_test_tx_group: default # 映射到Seata Server的集群名
grouplist:
default: 127.0.0.1:8091 # Seata Server地址
registry:
type: file
config:
type: file

# AT模式配置
enable-auto-data-source-proxy: true # 自动代理数据源
data-source-proxy-mode: AT # 代理模式

4. 创建undo_log表

每个需要参与分布式事务的数据库都要创建undo_log表:

-- 在业务数据库中创建undo_log表
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 '上下文',
`rollback_info` LONGBLOB NOT NULL COMMENT '回滚日志',
`log_status` INT(11) NOT NULL COMMENT '状态',
`log_created` DATETIME NOT NULL COMMENT '创建时间',
`log_modified` DATETIME NOT NULL COMMENT '修改时间',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT事务回滚日志表';

业务代码实现

订单服务 (TM)

@Service
public class OrderService {

@Autowired
private OrderMapper orderMapper;

@Autowired
private InventoryService inventoryService;

@Autowired
private AccountService accountService;

/**
* 下单业务 - 使用AT模式实现分布式事务
* @GlobalTransactional: Seata的全局事务注解
*/
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void placeOrder(String userId, String productId, Integer count, BigDecimal amount) {
log.info("开始下单, 用户:{}, 商品:{}, 数量:{}, 金额:{}", userId, productId, count, amount);

// 1. 创建订单 (本地事务1)
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
order.setAmount(amount);
order.setStatus(OrderStatus.INIT.getValue());
orderMapper.insert(order);
log.info("订单创建成功, 订单ID:{}", order.getId());

// 2. 扣减库存 (远程调用 - 库存服务)
inventoryService.deduct(productId, count);
log.info("库存扣减成功");

// 3. 扣减余额 (远程调用 - 账户服务)
accountService.deduct(userId, amount);
log.info("余额扣减成功");

// 4. 更新订单状态
order.setStatus(OrderStatus.SUCCESS.getValue());
orderMapper.updateById(order);
log.info("订单状态更新成功");

log.info("下单完成!");
}

/**
* 测试回滚场景
*/
@GlobalTransactional(rollbackFor = Exception.class)
public void placeOrderWithRollback(String userId, String productId,
Integer count, BigDecimal amount) {
// 1. 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
order.setAmount(amount);
orderMapper.insert(order);

// 2. 扣减库存
inventoryService.deduct(productId, count);

// 3. 扣减余额
accountService.deduct(userId, amount);

// 4. 模拟异常 - 触发回滚
if (amount.compareTo(new BigDecimal("100")) > 0) {
throw new RuntimeException("金额超过100,测试回滚");
}

// 如果抛出异常,Seata会自动回滚:
// - 删除订单记录
// - 恢复库存
// - 恢复余额
}
}

库存服务 (RM)

@Service
public class InventoryService {

@Autowired
private InventoryMapper inventoryMapper;

/**
* 扣减库存 - 作为分布式事务的分支事务
* 不需要任何特殊注解,Seata会自动拦截
*/
public void deduct(String productId, Integer count) {
log.info("开始扣减库存, 商品:{}, 数量:{}", productId, count);

// 查询库存
Inventory inventory = inventoryMapper.selectById(productId);
if (inventory == null) {
throw new RuntimeException("商品不存在");
}

// 检查库存是否充足
if (inventory.getCount() < count) {
throw new RuntimeException("库存不足");
}

// 扣减库存
inventory.setCount(inventory.getCount() - count);
inventoryMapper.updateById(inventory);

log.info("库存扣减成功, 剩余:{}", inventory.getCount());
}
}

账户服务 (RM)

@Service
public class AccountService {

@Autowired
private AccountMapper accountMapper;

/**
* 扣减余额 - 作为分布式事务的分支事务
*/
public void deduct(String userId, BigDecimal amount) {
log.info("开始扣减余额, 用户:{}, 金额:{}", userId, amount);

// 查询账户
Account account = accountMapper.selectById(userId);
if (account == null) {
throw new RuntimeException("账户不存在");
}

// 检查余额是否充足
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}

// 扣减余额
account.setBalance(account.getBalance().subtract(amount));
accountMapper.updateById(account);

log.info("余额扣减成功, 剩余:{}", account.getBalance());
}
}

AT模式的核心机制

1. 自动代理数据源

// Seata自动代理数据源,拦截SQL执行
@Configuration
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource")
public DataSource dataSource() {
// 原始数据源
DataSource originalDataSource = DataSourceBuilder.create().build();

// Seata代理数据源 - 自动拦截SQL,记录undo_log
return new DataSourceProxy(originalDataSource);
}
}

2. undo_log日志示例

-- 业务SQL执行前,记录before image
SELECT id, user_id, product_id, count, amount
FROM t_order
WHERE id = 1;

-- 执行业务SQL
UPDATE t_order
SET count = count - 1
WHERE id = 1;

-- 业务SQL执行后,记录after image
SELECT id, user_id, product_id, count, amount
FROM t_order
WHERE id = 1;

-- 插入undo_log (JSON格式存储)
INSERT INTO undo_log (xid, branch_id, rollback_info, ...)
VALUES ('xid...', 'branchId...', '{
"beforeImage": {
"rows": [{"fields": {"id": 1, "count": 10, ...}}]
},
"afterImage": {
"rows": [{"fields": {"id": 1, "count": 9, ...}}]
}
}', ...);

3. 回滚时生成反向SQL

// 如果需要回滚,Seata自动生成反向SQL
// beforeImage: count = 10
// afterImage: count = 9
// 回滚SQL:
UPDATE t_order
SET count = 10 -- 恢复为before image的值
WHERE id = 1;

AT模式的优缺点

优点:
├── ✅ 无侵入: 业务代码不需要改动
├── ✅ 使用简单: 只需添加@GlobalTransactional注解
├── ✅ 自动补偿: 自动记录和执行回滚
└── ✅ 性能可接受: 第一阶段释放本地锁

缺点:
├── ❌ 需要undo_log表: 每个数据库都要创建
├── ❌ 存在脏写风险: 两阶段之间存在时间窗口
├── ❌ 性能不如TCC: 需要解析SQL,记录镜像
└── ❌ 不适合复杂SQL: 复杂查询可能解析失败

Seata TCC模式实战

TCC模式原理

TCC (Try-Confirm-Cancel) 是一种应用层补偿型的分布式事务模式,需要业务方实现三个接口:

TCC两阶段提交:
├── 第一阶段: Try - 资源预留
│ ├── 完成业务检查
│ ├── 预留必须资源
│ └── 返回成功,表示可以进入第二阶段

└── 第二阶段:
├── Confirm: 确认执行 - 调用Try成功后执行
│ ├── 使用Try阶段预留的资源
│ ├── 真正执行业务操作
│ └── 幂等设计,支持重复调用

└── Cancel: 取消执行 - 调用Try失败后执行
├── 释放Try阶段预留的资源
├── 回滚业务操作
└── 幂等设计,支持重复调用

TCC模式实现

1. 定义TCC接口

/**
* 库存服务TCC接口
*/
@LocalTCC // 标记为TCC接口
public interface InventoryTccService {

/**
* Try阶段: 预留库存
* @BusinessActionContextParameter: 参数传递到Confirm/Cancel
*/
@TwoPhaseBusinessAction(
name = "inventoryDeductTcc", // 行为名称
commitMethod = "confirm", // Confirm方法名
rollbackMethod = "cancel" // Cancel方法名
)
boolean deduct(
BusinessActionContext businessActionContext,
@BusinessActionContextParameter(paramName = "productId") String productId,
@BusinessActionContextParameter(paramName = "count") Integer count
);

/**
* Confirm阶段: 确认扣减库存
* 使用Try阶段预留的库存
*/
boolean confirm(BusinessActionContext businessActionContext);

/**
* Cancel阶段: 取消扣减库存
* 释放Try阶段预留的库存
*/
boolean cancel(BusinessActionContext businessActionContext);
}

2. 实现TCC接口

@Service
public class InventoryTccServiceImpl implements InventoryTccService {

@Autowired
private InventoryMapper inventoryMapper;

@Autowired
private InventoryFreezeMapper inventoryFreezeMapper;

/**
* Try阶段: 预留库存
*/
@Transactional
@Override
public boolean deduct(BusinessActionContext businessActionContext,
String productId, Integer count) {
String xid = businessActionContext.getXid();
log.info("Try阶段开始, xid:{}, 商品:{}, 数量:{}", xid, productId, count);

// 1. 检查库存是否充足
Inventory inventory = inventoryMapper.selectById(productId);
if (inventory == null || inventory.getCount() < count) {
throw new RuntimeException("库存不足");
}

// 2. 冻结库存 - 从可用库存中扣减,转入冻结库存
int freezeResult = inventoryMapper.freezeInventory(productId, count);
if (freezeResult == 0) {
throw new RuntimeException("冻结库存失败");
}

// 3. 记录冻结日志 - 用于Confirm和Cancel
InventoryFreeze freeze = new InventoryFreeze();
freeze.setXid(xid);
freeze.setProductId(productId);
freeze.setCount(count);
freeze.setStatus(FreezeStatus.TRY.getValue());
inventoryFreezeMapper.insert(freeze);

log.info("Try阶段成功, xid:{}", xid);
return true;
}

/**
* Confirm阶段: 确认扣减库存
* 真正从冻结库存中扣减
*/
@Transactional
@Override
public boolean confirm(BusinessActionContext businessActionContext) {
String xid = businessActionContext.getXid();
String productId = (String) businessActionContext.getActionContext("productId");
Integer count = (Integer) businessActionContext.getActionContext("count");

log.info("Confirm阶段开始, xid:{}, 商品:{}, 数量:{}", xid, productId, count);

// 1. 幂等性检查 - 避免重复Confirm
InventoryFreeze freeze = inventoryFreezeMapper.selectByXid(xid);
if (freeze == null) {
log.info("冻结记录不存在,可能已Confirm, xid:{}", xid);
return true;
}

if (freeze.getStatus() == FreezeStatus.CONFIRM.getValue()) {
log.info("已经Confirm过,无需重复执行, xid:{}", xid);
return true;
}

// 2. 真正扣减库存 - 从冻结库存中扣减
inventoryMapper.deductFrozenInventory(productId, count);

// 3. 更新冻结状态
freeze.setStatus(FreezeStatus.CONFIRM.getValue());
inventoryFreezeMapper.updateById(freeze);

log.info("Confirm阶段成功, xid:{}", xid);
return true;
}

/**
* Cancel阶段: 取消扣减库存
* 释放冻结的库存
*/
@Transactional
@Override
public boolean cancel(BusinessActionContext businessActionContext) {
String xid = businessActionContext.getXid();
String productId = (String) businessActionContext.getActionContext("productId");
Integer count = (Integer) businessActionContext.getActionContext("count");

log.info("Cancel阶段开始, xid:{}, 商品:{}, 数量:{}", xid, productId, count);

// 1. 幂等性检查 - 避免重复Cancel
InventoryFreeze freeze = inventoryFreezeMapper.selectByXid(xid);
if (freeze == null) {
log.info("冻结记录不存在,可能已Cancel, xid:{}", xid);
return true;
}

if (freeze.getStatus() == FreezeStatus.CANCEL.getValue()) {
log.info("已经Cancel过,无需重复执行, xid:{}", xid);
return true;
}

// 2. 释放冻结库存 - 恢复到可用库存
inventoryMapper.releaseFrozenInventory(productId, count);

// 3. 更新冻结状态
freeze.setStatus(FreezeStatus.CANCEL.getValue());
inventoryFreezeMapper.updateById(freeze);

log.info("Cancel阶段成功, xid:{}", xid);
return true;
}
}

3. 数据库表设计

-- 库存表
CREATE TABLE `t_inventory` (
`id` varchar(64) NOT NULL COMMENT '商品ID',
`count` int(11) NOT NULL COMMENT '可用库存',
`freeze_count` int(11) NOT NULL DEFAULT '0' COMMENT '冻结库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 库存冻结记录表
CREATE TABLE `t_inventory_freeze` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`xid` varchar(128) NOT NULL COMMENT '全局事务ID',
`product_id` varchar(64) NOT NULL COMMENT '商品ID',
`count` int(11) NOT NULL COMMENT '冻结数量',
`status` tinyint(4) NOT NULL COMMENT '状态: 1-Try, 2-Confirm, 3-Cancel',
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

4. Mapper接口

@Mapper
public interface InventoryMapper {

/**
* 冻结库存 - Try阶段
* 从可用库存转移到冻结库存
*/
@Update("UPDATE t_inventory " +
"SET count = count - #{count}, " +
" freeze_count = freeze_count + #{count} " +
"WHERE id = #{productId} " +
"AND count >= #{count}") -- 乐观锁
int freezeInventory(@Param("productId") String productId,
@Param("count") Integer count);

/**
* 扣减冻结库存 - Confirm阶段
* 从冻结库存中真正扣减
*/
@Update("UPDATE t_inventory " +
"SET freeze_count = freeze_count - #{count} " +
"WHERE id = #{productId} " +
"AND freeze_count >= #{count}")
int deductFrozenInventory(@Param("productId") String productId,
@Param("count") Integer count);

/**
* 释放冻结库存 - Cancel阶段
* 从冻结库存恢复到可用库存
*/
@Update("UPDATE t_inventory " +
"SET count = count + #{count}, " +
" freeze_count = freeze_count - #{count} " +
"WHERE id = #{productId} " +
"AND freeze_count >= #{count}")
int releaseFrozenInventory(@Param("productId") String productId,
@Param("count") Integer count);
}

TCC模式使用

@Service
public class OrderService {

@Autowired
private InventoryTccService inventoryTccService;

@Autowired
private AccountTccService accountTccService;

/**
* 使用TCC模式下单
*/
@GlobalTransactional(name = "create-order-tcc", rollbackFor = Exception.class)
public void placeOrderWithTcc(String userId, String productId,
Integer count, BigDecimal amount) {
log.info("开始TCC下单");

// 1. 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
order.setAmount(amount);
orderMapper.insert(order);

// 2. Try阶段 - 预留资源
inventoryTccService.deduct(null, productId, count); // 预留库存
accountTccService.deduct(null, userId, amount); // 预留余额

// 3. 如果上述Try都成功,Seata会自动调用Confirm
// 如果任何一个Try失败,Seata会自动调用Cancel

log.info("TCC下单完成");
}
}

TCC模式的优缺点

优点:
├── ✅ 性能高: 第一阶段就释放资源
├── ✅ 无锁: 不需要数据库锁
├── ✅ 灵活: 业务方自己控制补偿逻辑
└── ✅ 适用范围广: 适合复杂业务

缺点:
├── ❌ 代码侵入性强: 需要编写三个方法
├── ❌ 开发成本高: 需要考虑幂等性、悬挂等
├── ❌ 容易出错: 补偿逻辑需要仔细设计
└── ❌ 维护成本高: 业务变更需要同步修改TCC

TCC模式注意事项

1. 幂等性设计

// Confirm/Cancel必须支持幂等
public boolean confirm(BusinessActionContext context) {
// 先检查是否已处理
if (isAlreadyProcessed(context.getXid())) {
return true; // 已处理过,直接返回成功
}

// 执行业务逻辑
doConfirm();

// 标记为已处理
markAsProcessed(context.getXid());
return true;
}

2. 空回滚处理

// Try还没执行,Cancel先到了(网络延迟等原因)
public boolean cancel(BusinessActionContext context) {
String xid = context.getXid();

// 检查是否有Try记录
if (freezeMapper.selectByXid(xid) == null) {
// 没有Try记录,插入一条Cancel记录,避免空回滚
InventoryFreeze freeze = new InventoryFreeze();
freeze.setXid(xid);
freeze.setStatus(FreezeStatus.CANCEL.getValue());
freezeMapper.insert(freeze);

return true; // 空回滚成功
}

// 正常Cancel逻辑
doCancel(context);
return true;
}

3. 悬挂处理

// Cancel先执行了,Try后执行
public boolean deduct(BusinessActionContext context, String productId, Integer count) {
String xid = context.getXid();

// 检查是否已经Cancel
InventoryFreeze freeze = freezeMapper.selectByXid(xid);
if (freeze != null && freeze.getStatus() == FreezeStatus.CANCEL.getValue()) {
// 已经Cancel,不允许Try
throw new RuntimeException("已取消,不允许Try");
}

// 正常Try逻辑
doTry(productId, count);
return true;
}

Seata SAGA模式实战

SAGA模式原理

SAGA模式是一种长事务解决方案,通过定义状态机来编排业务流程,每个步骤都有对应的补偿操作。

SAGA模式核心概念:
├── 状态机(State Machine): 定义业务流程
├── 状态(State): 每个业务步骤
├── 转移(Transition): 状态之间的流转
├── 补偿(Compensation): 失败时的回滚操作
└── 事件(Event): 触发状态转移的事件

SAGA状态机定义

{
"Name": "purchaseOrderFlow",
"Comment": "下单流程",
"StartState": "createOrder",
"States": {
"createOrder": {
"Type": "ServiceTask",
"ServiceName": "orderService",
"ServiceMethod": "create",
"CompensateState": "compensateCreateOrder",
"Next": "deductInventory",
"Input": ["$input.userId", "$input.productId", "$input.count"],
"Output": {
"orderId": "$.orderId"
}
},
"deductInventory": {
"Type": "ServiceTask",
"ServiceName": "inventoryService",
"ServiceMethod": "deduct",
"CompensateState": "compensateDeductInventory",
"Next": "deductBalance",
"Input": ["$input.productId", "$input.count"],
"Output": {
"inventory": "$.inventory"
}
},
"deductBalance": {
"Type": "ServiceTask",
"ServiceName": "accountService",
"ServiceMethod": "deduct",
"CompensateState": "compensateDeductBalance",
"Next": "updateOrderStatus",
"Input": ["$input.userId", "$input.amount"],
"Output": {
"balance": "$.balance"
}
},
"updateOrderStatus": {
"Type": "ServiceTask",
"ServiceName": "orderService",
"ServiceMethod": "updateStatus",
"CompensateState": "compensateUpdateOrderStatus",
"Input": ["$.orderId"],
"IsEnd": true
},
"compensateCreateOrder": {
"Type": "Compensation",
"ServiceName": "orderService",
"ServiceMethod": "delete"
},
"compensateDeductInventory": {
"Type": "Compensation",
"ServiceName": "inventoryService",
"ServiceMethod": "addBack"
},
"compensateDeductBalance": {
"Type": "Compensation",
"ServiceName": "accountService",
"ServiceMethod": "refund"
},
"compensateUpdateOrderStatus": {
"Type": "Compensation",
"ServiceName": "orderService",
"ServiceMethod": "revertStatus"
}
}
}

SAGA业务实现

@Service
public class OrderService {

/**
* 正向操作 - 创建订单
*/
public Order create(String userId, String productId, Integer count) {
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
order.setStatus(OrderStatus.INIT.getValue());
orderMapper.insert(order);
return order;
}

/**
* 补偿操作 - 删除订单
*/
public void delete(Order order) {
orderMapper.deleteById(order.getId());
}

/**
* 正向操作 - 更新订单状态
*/
public void updateStatus(Long orderId) {
Order order = new Order();
order.setId(orderId);
order.setStatus(OrderStatus.SUCCESS.getValue());
orderMapper.updateById(order);
}

/**
* 补偿操作 - 恢复订单状态
*/
public void revertStatus(Long orderId) {
Order order = new Order();
order.setId(orderId);
order.setStatus(OrderStatus.CANCEL.getValue());
orderMapper.updateById(order);
}
}

@Service
public class InventoryService {

/**
* 正向操作 - 扣减库存
*/
public Inventory deduct(String productId, Integer count) {
Inventory inventory = inventoryMapper.selectById(productId);
inventory.setCount(inventory.getCount() - count);
inventoryMapper.updateById(inventory);
return inventory;
}

/**
* 补偿操作 - 恢复库存
*/
public void addBack(String productId, Integer count) {
Inventory inventory = inventoryMapper.selectById(productId);
inventory.setCount(inventory.getCount() + count);
inventoryMapper.updateById(inventory);
}
}

@Service
public class AccountService {

/**
* 正向操作 - 扣减余额
*/
public Account deduct(String userId, BigDecimal amount) {
Account account = accountMapper.selectById(userId);
account.setBalance(account.getBalance().subtract(amount));
accountMapper.updateById(account);
return account;
}

/**
* 补偿操作 - 退款
*/
public void refund(String userId, BigDecimal amount) {
Account account = accountMapper.selectById(userId);
account.setBalance(account.getBalance().add(amount));
accountMapper.updateById(account);
}
}

SAGA模式使用

@Service
public class OrderBusinessService {

@Autowired
private StateMachineEngine stateMachineEngine;

/**
* 使用SAGA模式下单
*/
public void placeOrderWithSaga(String userId, String productId,
Integer count, BigDecimal amount) {
// 构建输入参数
Map<String, Object> startParams = new HashMap<>();
startParams.put("userId", userId);
startParams.put("productId", productId);
startParams.put("count", count);
startParams.put("amount", amount);

// 启动状态机
StateMachineInstance instance = stateMachineEngine.start(
"purchaseOrderFlow", // 状态机名称
null, // 业务键
startParams // 输入参数
);

// 检查执行结果
if (instance.getStatus() == ExecutionStatus.SU) {
log.info("下单成功");
} else {
log.error("下单失败, 状态:{}", instance.getStatus());
throw new RuntimeException("下单失败");
}
}
}

SAGA模式的优缺点

优点:
├── ✅ 适合长事务: 可包含几十个步骤
├── ✅ 可观测性好: 状态机可视化
├── ✅ 灵活: 支持串行、并行、子流程
└── ✅ 无锁: 不占用数据库锁

缺点:
├── ❌ 开发成本高: 需要定义状态机
├── ❌ 学习曲线陡: 需要理解状态机
├── ❌ 补偿复杂: 每个步骤都需要补偿
└── ❌ 不支持隔离: 无法实现可串行化

实战场景应用

场景1: 电商下单

@Service
public class EcommerceOrderService {

/**
* 电商下单 - 典型的分布式事务场景
* 涉及: 订单服务、库存服务、账户服务、优惠券服务、积分服务
*/
@GlobalTransactional(name = "ecommerce-order", rollbackFor = Exception.class)
public void placeOrder(OrderRequest request) {
// 1. 校验优惠券
couponService.useCoupon(request.getUserId(), request.getCouponId());

// 2. 创建订单
Order order = createOrder(request);

// 3. 扣减库存
inventoryService.deduct(order.getProductId(), order.getCount());

// 4. 扣减余额
accountService.deduct(order.getUserId(), order.getPayAmount());

// 5. 增加积分
pointService.add(order.getUserId(), order.getPayAmount());

// 6. 发送通知
notificationService.sendOrderMessage(order);
}
}

场景2: 跨行转账

@Service
public class TransferService {

/**
* 跨行转账 - 强一致性要求
* 建议: 使用XA模式或TCC模式
*/
@GlobalTransactional(name = "bank-transfer", rollbackFor = Exception.class)
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
// 1. 转出账户扣款
Account from = accountService.query(fromAccount);
if (from.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}
accountService.deduct(fromAccount, amount);

// 2. 转入账户收款
accountService.add(toAccount, amount);

// 3. 记录转账流水
TransferRecord record = new TransferRecord();
record.setFromAccount(fromAccount);
record.setToAccount(toAccount);
record.setAmount(amount);
record.setStatus(TransferStatus.SUCCESS);
transferRecordMapper.insert(record);
}
}

场景3: 订单超时取消

@Service
public class OrderTimeoutService {

/**
* 订单超时取消 - SAGA模式
* 涉及: 取消订单、恢复库存、退款、恢复优惠券、扣减积分
*/
public void cancelOrder(Long orderId) {
Order order = orderMapper.selectById(orderId);

// 使用SAGA模式执行复杂的取消流程
Map<String, Object> params = new HashMap<>();
params.put("orderId", orderId);
params.put("userId", order.getUserId());
params.put("productId", order.getProductId());
params.put("count", order.getCount());
params.put("amount", order.getAmount());
params.put("couponId", order.getCouponId());

StateMachineInstance instance = stateMachineEngine.start(
"cancelOrderFlow",
orderId.toString(),
params
);

if (instance.getStatus() != ExecutionStatus.SU) {
log.error("订单取消失败, orderId:{}", orderId);
throw new RuntimeException("订单取消失败");
}
}
}

场景4: 旅游预订

@Service
public class TravelBookingService {

/**
* 旅游预订 - 涉及多个外部服务
* 航班、酒店、景点、保险、接送机
*/
@GlobalTransactional(name = "travel-booking", rollbackFor = Exception.class)
public void bookTravel(TravelBookingRequest request) {
// 1. 预订航班
flightService.book(request.getFlightId(), request.getPassengers());

// 2. 预订酒店
hotelService.book(request.getHotelId(), request.getCheckInDate(),
request.getCheckOutDate(), request.getRooms());

// 3. 预订景点门票
attractionService.book(request.getAttractionIds(),
request.getVisitDate());

// 4. 购买保险
insuranceService.buy(request.getInsurancePlan(),
request.getPassengers());

// 5. 预订接送机
transferService.book(request.getTransferId(),
request.getFlightTime());

// 6. 创建行程单
itineraryService.create(request);
}
}

面试核心问题

Q1: 什么是分布式事务?为什么需要分布式事务?

参考答案:

分布式事务是指涉及多个独立数据库或服务的事务操作,需要保证这些操作的原子性,要么全部成功,要么全部失败。

在微服务架构中,业务被拆分成多个独立的服务,每个服务有独立的数据库。一个业务操作可能需要调用多个服务,如果不使用分布式事务,就可能出现数据不一致的问题。

举例: 下单时,订单创建成功,但库存扣减失败,就会出现超卖问题。

Q2: 分布式事务有哪些解决方案?各有什么优缺点?

参考答案:

方案优点缺点适用场景
2PC/XA强一致,实现简单性能差,阻塞,单点故障金融系统
TCC性能好,灵活代码侵入,开发成本高高并发核心业务
SAGA适合长事务状态机复杂,补偿难长流程业务
本地消息表可靠,简单需要定时任务异步解耦
AT模式无侵入,简单需要undo_log,脏写风险一般业务

Q3: Seata的AT模式是如何实现的?

参考答案:

AT模式分为两个阶段:

第一阶段:

  1. 解析SQL,找到要修改的行,记录before image
  2. 执行业务SQL
  3. 查询修改后的数据,记录after image
  4. 将before和after镜像存入undo_log表
  5. 提交本地事务,释放本地锁

第二阶段:

  • 提交: 异步删除undo_log,释放全局锁
  • 回滚: 读取undo_log,生成反向SQL,执行回滚,删除undo_log

核心: 通过undo_log实现自动补偿,对业务代码零侵入。

Q4: Seata的TCC模式如何实现幂等性?

参考答案:

TCC的Confirm和Cancel需要支持幂等性,因为网络重试可能导致多次调用。

实现方法:

  1. 数据库唯一约束: 在业务表中增加xid字段,设置唯一索引
  2. 状态机记录: 记录每个分支事务的执行状态
  3. 先检查再执行: 每次执行前先检查是否已执行过
public boolean confirm(BusinessActionContext context) {
String xid = context.getXid();

// 幂等检查
Record record = mapper.selectByXid(xid);
if (record != null && record.getStatus() == Status.CONFIRMED) {
return true; // 已执行,直接返回
}

// 执行业务
doConfirm();

// 更新状态
record.setStatus(Status.CONFIRMED);
mapper.updateById(record);

return true;
}

Q5: 什么是CAP定理?分布式系统如何权衡?

参考答案:

CAP定理指出,分布式系统不可能同时满足一致性(C)、可用性(A)、分区容错性(P):

  • CP: 放弃可用性,保证一致性

    • 代表: XA、Seata XA模式
    • 场景: 金融交易、支付系统
  • AP: 放弃强一致性,保证可用性

    • 代表: TCC、SAGA、最终一致性
    • 场景: 社交媒体、内容分发
  • CA: 放弃分区容错

    • 代表: 单体应用、RDBMS
    • 场景: 传统应用

在分布式系统中,网络分区是必然的,所以只能在CP和AP之间权衡

Q6: 什么是BASE理论?

参考答案:

BASE理论是对CAP理论的补充,提倡采用最终一致性:

  • BA (Basically Available): 基本可用,允许损失部分可用性
  • S (Soft State): 软状态,允许数据存在中间状态
  • E (Eventually Consistent): 最终一致性,数据最终会达到一致

与ACID的区别:

  • ACID追求强一致性,刚性事务
  • BASE接受最终一致性,柔性事务

Q7: 分布式事务如何处理超时和重试?

参考答案:

  1. 超时处理:

    • 设置合理的超时时间
    • 超时后触发回滚或补偿
    • Seata通过全局锁检测超时
  2. 重试机制:

    • 最大重试次数限制
    • 指数退避策略
    • 幂等性保证
@GlobalTransactional(
timeoutMills = 60000, // 超时时间60秒
rollbackFor = Exception.class
)
public void business() {
// 业务逻辑
}

Q8: 如何保证分布式事务的可靠性?

参考答案:

  1. 幂等性: 所有接口都要支持幂等
  2. 超时控制: 合理设置超时时间
  3. 重试机制: 失败自动重试
  4. 事务日志: 记录完整的执行过程
  5. 监控告警: 监控事务成功率、耗时等
  6. 降级策略: 失败时的降级方案
  7. 数据校验: 定期对账,发现不一致

Q9: TCC模式的空回滚和悬挂问题如何解决?

参考答案:

空回滚: Cancel先执行,Try还没执行

  • 解决: Cancel时检查是否有Try记录,没有则插入一条Cancel记录

悬挂: Cancel先执行了,Try后执行

  • 解决: Try时检查是否已Cancel,已Cancel则拒绝Try
// 空回滚处理
public boolean cancel(BusinessActionContext context) {
String xid = context.getXid();
if (mapper.selectByXid(xid) == null) {
// 没有Try记录,插入Cancel记录
insertCancelRecord(xid);
return true;
}
// 正常Cancel逻辑
}

// 悬挂处理
public boolean try(BusinessActionContext context) {
String xid = context.getXid();
Record record = mapper.selectByXid(xid);
if (record != null && record.getStatus() == Status.CANCELED) {
throw new RuntimeException("已Cancel,不允许Try");
}
// 正常Try逻辑
}

Q10: 分布式事务的性能如何优化?

参考答案:

  1. 选择合适的模式: 普通业务用AT,高性能用TCC
  2. 减少事务范围: 只将必要操作纳入事务
  3. 异步提交: 使用Seata的异步提交
  4. 优化SQL: 减少锁的持有时间
  5. 合并请求: 批量处理减少RPC调用
  6. 缓存优化: 减少数据库访问
// 减少事务范围
public void placeOrder(OrderRequest request) {
// 不在事务中的操作
validateRequest(request);
calculatePrice(request);

// 在事务中的核心操作
doPlaceOrder(request);

// 不在事务中的操作
sendNotification(request);
}

最佳实践与避坑指南

最佳实践

1. 选择合适的事务模式

模式选择决策树:
是否需要强一致性?
├── 是 → 使用XA模式
└── 否 → 是否高并发场景?
├── 是 → 使用TCC模式
└── 否 → 是否长事务?
├── 是 → 使用SAGA模式
└── 否 → 使用AT模式(推荐)

2. 合理设置超时时间

// ❌ 不合理: 超时时间太长
@GlobalTransactional(timeoutMills = 300000) // 5分钟

// ✅ 合理: 根据业务设置
@GlobalTransactional(timeoutMills = 60000) // 1分钟

3. 幂等性设计

// ✅ 所有分布式事务接口都要支持幂等
@Service
public class InventoryService {

public void deduct(String productId, Integer count) {
// 使用数据库唯一索引保证幂等
// 或使用Redis分布式锁
// 或使用状态机记录
}
}

4. 异常处理

@GlobalTransactional(rollbackFor = Exception.class)
public void placeOrder(OrderRequest request) {
try {
// 业务逻辑
} catch (BusinessException e) {
// 业务异常,回滚
throw e;
} catch (Exception e) {
// 系统异常,回滚
log.error("下单失败", e);
throw e;
}
}

5. 监控告警

@Component
public class TransactionMonitor {

/**
* 监控分布式事务的执行情况
*/
public void monitor(GlobalTransactionContext context) {
// 记录事务执行时间
// 记录事务成功率
// 记录回滚原因
// 异常情况告警
}
}

避坑指南

1. 避免大事务

// ❌ 错误: 事务中包含太多操作
@GlobalTransactional
public void badBigTransaction() {
operation1();
operation2();
operation3();
// ... 几十个操作
operation20();
}

// ✅ 正确: 拆分成多个小事务
public void goodSmallTransaction() {
subTransaction1();
subTransaction2();
subTransaction3();
}

2. 避免跨库JOIN

// ❌ 错误: 在分布式事务中跨库查询
@GlobalTransactional
public void badQuery() {
List<Data> data = repository.crossDbJoin();
}

// ✅ 正确: 在应用层组装数据
public void goodQuery() {
Data1 data1 = service1.query();
Data2 data2 = service2.query();
// 在应用层组装
}

3. 避免在事务中调用外部服务

// ❌ 错误: 在事务中调用第三方API
@GlobalTransactional
public void badExternalCall() {
orderMapper.insert(order);
thirdPartyPaymentService.pay(); // 可能很慢或超时
}

// ✅ 正确: 将外部调用放在事务外
public void goodExternalCall() {
createOrder();
paymentService.pay(); // 事务外调用
}

4. 避免忽略回滚日志

// ❌ 错误: 没有创建undo_log表
// AT模式会失败

// ✅ 正确: 每个数据库都创建undo_log表
CREATE TABLE `undo_log` (...);

5. 避免TCC实现不完整

// ❌ 错误: TCC只实现了Try,没有实现Confirm和Cancel
@LocalTCC
public interface BadTccService {
@TwoPhaseBusinessAction(
commitMethod = "confirm",
rollbackMethod = "cancel"
)
boolean tryMethod();

// 缺少confirm和cancel的实现
}

// ✅ 正确: 完整实现Try、Confirm、Cancel
@LocalTCC
public interface GoodTccService {
@TwoPhaseBusinessAction(
commitMethod = "confirm",
rollbackMethod = "cancel"
)
boolean tryMethod();

boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}

总结

分布式事务是微服务架构中的核心技术,Seata提供了AT、TCC、SAGA、XA四种模式,可以满足不同场景的需求:

学习路线:
├── 1. 理解分布式事务的基础理论
│ ├── CAP定理
│ ├── BASE理论
│ └── 一致性模型

├── 2. 掌握Seata的基本使用
│ ├── 安装部署Seata Server
│ ├── 配置数据源代理
│ └── 创建undo_log表

├── 3. 深入AT模式
│ ├── 理解两阶段提交原理
│ ├── 掌握undo_log机制
│ └── 实际项目应用

├── 4. 学习TCC模式(进阶)
│ ├── 理解Try-Confirm-Cancel
│ ├── 掌握幂等性设计
│ ├── 解决空回滚和悬挂
│ └── 高并发场景应用

├── 5. 了解SAGA模式(高级)
│ ├── 理解状态机
│ ├── 定义业务流程
│ └── 长事务场景应用

└── 6. 生产实践
├── 性能优化
├── 监控告警
└── 问题排查

核心要点:

  1. 根据场景选择合适的模式: AT最简单,TCC性能最好,SAGA适合长事务
  2. 所有接口都要幂等: 分布式环境中重试是常态
  3. 合理设置超时: 避免长时间占用锁
  4. 完善的监控: 及时发现和处理问题
  5. 充分的测试: 各种异常场景都要测试

掌握分布式事务,能够帮助你在微服务架构中构建可靠、一致的业务系统!