知识模块
☕ Java 知识模块
八、Spring 全家桶
Seata 分布式事务

Seata 分布式事务

什么是分布式事务?

在微服务架构中,一个业务操作涉及多个服务,需要保证数据一致性。

订单服务 → 创建订单

库存服务 → 扣减库存

账户服务 → 扣减余额

任意一步失败,都需要全部回滚

Seata 简介

Seata 是阿里巴巴开源的分布式事务解决方案,提供:

  • AT 模式:无侵入,自动补偿
  • TCC 模式:高性能,业务补偿
  • SAGA 模式:长事务,状态机

AT 模式

原理

一阶段:
1. 解析 SQL,记录数据修改前的快照
2. 执行业务 SQL
3. 记录数据修改后的快照
4. 生成行锁
5. 注册分支事务到 TC

二阶段-提交:
1. 异步删除快照和行锁

二阶段-回滚:
1. 对比当前数据与修改后快照
2. 如果一致,用修改前快照回滚
3. 如果不一致,说明有脏写,需人工处理

快速开始

1. 安装 Seata Server

# 下载
wget https://github.com/seata/seata/releases/download/v1.7.0/seata-server-1.7.0.zip
 
# 解压并启动
unzip seata-server-1.7.0.zip
cd seata-server-1.7.0
sh bin/seata-server.sh

2. 添加依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

3. 配置

seata:
  enabled: true
  tx-service-group: my_tx_group
  service:
    vgroup-mapping:
      my_tx_group: default
  registry:
    type: nacos
    nacos:
      server-addr: localhost:8848
      namespace: ""
      group: SEATA_GROUP

4. 使用

@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private StorageClient storageClient;
    
    @Autowired
    private AccountClient accountClient;
    
    @GlobalTransactional  // 开启全局事务
    public void createOrder(OrderDTO orderDTO) {
        // 1. 创建订单
        Order order = new Order();
        order.setUserId(orderDTO.getUserId());
        order.setProductId(orderDTO.getProductId());
        order.setCount(orderDTO.getCount());
        order.setMoney(orderDTO.getMoney());
        orderMapper.insert(order);
        
        // 2. 扣减库存(远程调用)
        storageClient.decrease(orderDTO.getProductId(), orderDTO.getCount());
        
        // 3. 扣减余额(远程调用)
        accountClient.decrease(orderDTO.getUserId(), orderDTO.getMoney());
        
        // 4. 更新订单状态
        order.setStatus(1);
        orderMapper.updateById(order);
    }
}

数据库表

-- 每个库都需要
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB;

TCC 模式

原理

Try:预留资源
Confirm:确认提交
Cancel:取消预留

实现

public interface StorageTccService {
    
    @TwoPhaseBusinessAction(name = "prepareDeduct", 
                           commitMethod = "commit", 
                           rollbackMethod = "rollback")
    boolean prepareDeduct(@BusinessActionContextParameter(paramName = "productId") Long productId,
                         @BusinessActionContextParameter(paramName = "count") Integer count);
    
    boolean commit(BusinessActionContext context);
    
    boolean rollback(BusinessActionContext context);
}
 
@Service
public class StorageTccServiceImpl implements StorageTccService {
    
    @Autowired
    private StorageMapper storageMapper;
    
    @Override
    public boolean prepareDeduct(Long productId, Integer count) {
        // 预留库存(冻结)
        Storage storage = storageMapper.selectById(productId);
        if (storage.getStock() < count) {
            throw new RuntimeException("库存不足");
        }
        storage.setStock(storage.getStock() - count);
        storage.setFrozenStock(storage.getFrozenStock() + count);
        storageMapper.updateById(storage);
        return true;
    }
    
    @Override
    public boolean commit(BusinessActionContext context) {
        // 确认扣减(清除冻结)
        Long productId = context.getActionContext("productId", Long.class);
        Integer count = context.getActionContext("count", Integer.class);
        
        Storage storage = storageMapper.selectById(productId);
        storage.setFrozenStock(storage.getFrozenStock() - count);
        storageMapper.updateById(storage);
        return true;
    }
    
    @Override
    public boolean rollback(BusinessActionContext context) {
        // 回滚(恢复库存)
        Long productId = context.getActionContext("productId", Long.class);
        Integer count = context.getActionContext("count", Integer.class);
        
        Storage storage = storageMapper.selectById(productId);
        storage.setStock(storage.getStock() + count);
        storage.setFrozenStock(storage.getFrozenStock() - count);
        storageMapper.updateById(storage);
        return true;
    }
}

AT vs TCC

对比AT 模式TCC 模式
侵入性高(需编写补偿代码)
性能较低(需要解析 SQL)
一致性最终一致最终一致
适用场景大多数业务高性能要求

面试高频问题

Q1: 分布式事务有哪些方案?

方案说明
2PC两阶段提交,强一致,性能低
3PC三阶段提交,改进 2PC
TCCTry-Confirm-Cancel,高性能
SAGA长事务,补偿机制
本地消息表最终一致
Seata AT无侵入,自动补偿

Q2: Seata AT 模式的原理?

  1. 一阶段:执行 SQL + 记录快照
  2. 二阶段提交:删除快照
  3. 二阶段回滚:用快照恢复数据

Q3: AT 模式如何防止脏写?

通过全局锁:

  • 提交前检查全局锁
  • 如果被其他事务锁定,等待或回滚

Q4: TCC 的空回滚和悬挂问题?

空回滚:Try 未执行,Cancel 却执行了 悬挂:Cancel 比 Try 先执行

解决:通过事务状态表记录状态

总结

Seata 核心要点:
1. 三种模式:AT(推荐)、TCC、SAGA
2. AT 模式:无侵入,自动补偿
3. TCC 模式:高性能,需编写补偿代码
4. 注解:@GlobalTransactional
5. 关键表:undo_log