Skip to content
第 15 章 架构 ⏱ 12 分钟阅读

第 15 章:Seata 实战 ​

学习目标 ​

  • 掌握 Seata AT/TCC/Saga/XA 模式
  • 学会部署 Seata Server
  • 用 Spring Cloud 集成 Seata 解决分布式事务
  • 避免 AT 模式脏写、隔离级别不足的坑

一、Seata 是什么? ​

阿里开源的分布式事务中间件,提供了 AT、TCC、Saga、XA 四种模式。

text
应用层
   ↓
[Seata Client(TC)]
   ↓
[Seata Server(TC)]
   ├── 协调全局事务
   ├── 记录分支事务
   └── 推动 Commit/Rollback

二、AT 模式(默认,推荐) ​

工作机制:两阶段 + 写前镜像 + 写后镜像

text
阶段 1:解析 SQL,生成 undo log,直接提交本地事务
阶段 2:全局事务提交,删除 undo log;或回滚,根据 undo 还原
xml
<!-- 引入依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
yaml
# application.yml
spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: order_tx_group
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/order_db
    username: root
    password: root
java
// 订单服务
@Service
public class OrderService {
    @Resource
    private OrderRepository orderRepository;

    @GlobalTransactional(name = "place-order", rollbackFor = Exception.class)
    public void placeOrder(OrderDto dto) {
        // 1. 订单服务本地事务
        orderRepository.save(new Order(dto));

        // 2. 远程调用扣库存(走 Seata 代理)
        inventoryClient.deduct(dto.skuId, dto.quantity);
    }
}
java
// 库存服务
@Service
public class InventoryService {
    @Transactional
    public void deduct(Long skuId, int quantity) {
        inventoryRepository.deduct(skuId, quantity);
    }
}

⚠️ 坑 1:AT 模式依赖undo log 表,每个业务库都要建 undo_log 表,否则全局回滚找不到快照。

sql
-- 每个业务库都要建
CREATE TABLE undo_log (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    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,
    UNIQUE KEY ux_undo_log (xid, branch_id)
);

三、Seata Server 部署 ​

yaml
# docker-compose.yml
version: '3.8'
services:
  seata-server:
    image: seataio/seata-server:1.8.0
    ports:
      - "8091:8091"
    environment:
      - SEATA_PORT=8091
      - STORE_MODE=db
      - DB_URL=jdbc:mysql://mysql:3306/seata
      - DB_USER=root
      - DB_PASSWORD=root
sql
-- Seata Server 数据库
CREATE TABLE branch_table (
    branch_id BIGINT PRIMARY KEY,
    xid VARCHAR(128) NOT NULL,
    transaction_id BIGINT,
    resource_group_id VARCHAR(32),
    resource_id VARCHAR(256),
    branch_type VARCHAR(8),
    status TINYINT,
    client_id VARCHAR(64),
    application_data VARCHAR(2000),
    gmt_create DATETIME,
    gmt_modified DATETIME
);

CREATE TABLE global_table (
    xid VARCHAR(128) PRIMARY KEY,
    transaction_id BIGINT,
    status TINYINT NOT NULL,
    application_id VARCHAR(32),
    transaction_service_group VARCHAR(32),
    transaction_name VARCHAR(128),
    timeout INT,
    begin_time BIGINT,
    application_data VARCHAR(2000),
    gmt_create DATETIME,
    gmt_modified DATETIME
);

四、AT 隔离级别与脏写 ​

AT 模式默认读未提交,可能出现脏写:

java
// 场景:两个事务同时改同一行
// 事务 A:UPDATE product SET stock = 90
// 事务 B:UPDATE product SET stock = 80
// A 回滚,B 提交 → 最终 80,正确

// 但如果:
// 事务 A:UPDATE product SET stock = 90(后回滚)
// 事务 B:UPDATE product SET stock = 80(后提交)
// A 的写前镜像记录 stock=100,回滚还原 100
// B 提交后是 80,回滚后是 100 ❌ B 的修改被回滚覆盖
java
// 解决:加全局锁
@GlobalLock
public Product getProduct(Long id) {
    return productRepository.findById(id);
}

// 写时强制 SELECT FOR UPDATE
@GlobalTransactional
public void updateProduct(Long id, int newStock) {
    Product p = productRepository.findByIdForUpdate(id);  // SELECT FOR UPDATE
    p.setStock(newStock);
    productRepository.save(p);
}

⚠️ 坑 2:AT 模式不保证"读已提交",如需防止脏读,业务 SQL 加 FOR UPDATE。

五、TCC 模式 ​

java
// 订单服务 TCC
@LocalTCC
public interface OrderTccAction {
    @TwoPhaseBusinessAction(name = "orderTry", commitMethod = "confirm", rollbackMethod = "cancel")
    boolean tryOrder(BusinessActionContext context, @BusinessActionContextParameter Long orderId);

    boolean confirmOrder(BusinessActionContext context);
    boolean cancelOrder(BusinessActionContext context);
}

@Component
public class OrderTccActionImpl implements OrderTccAction {
    @Override
    public boolean tryOrder(BusinessActionContext context, Long orderId) {
        // 订单状态 = PENDING
        orderDao.updateStatus(orderId, "PENDING");
        return true;
    }

    @Override
    public boolean confirmOrder(BusinessActionContext context) {
        orderDao.updateStatus(context.getActionContext("orderId", Long.class), "CONFIRMED");
        return true;
    }

    @Override
    public boolean cancelOrder(BusinessActionContext context) {
        orderDao.updateStatus(context.getActionContext("orderId", Long.class), "CANCELLED");
        return true;
    }
}
java
// 调用方
@GlobalTransactional
public void placeOrder(OrderDto dto) {
    orderTccAction.tryOrder(null, dto.id);
    inventoryTccAction.tryFreeze(null, dto.skuId, dto.quantity);
}

六、Saga 模式 ​

java
// 状态机定义
public class OrderSaga {
    @SagaStart
    public void placeOrder(OrderDto dto) {
        // 1. 订单
        orderService.create(dto);
        // 2. 支付
        paymentService.pay(dto);
        // 3. 库存
        inventoryService.deduct(dto);
    }

    @Compensation(forward = "placeOrder", compensation = "cancelOrder")
    public void cancelOrder(OrderDto dto) {
        orderService.cancel(dto);
        paymentService.refund(dto);
        inventoryService.restore(dto);
    }
}
text
优点:长事务、跨服务
缺点:补偿逻辑靠业务写,容易遗漏

七、XA 模式(强一致) ​

java
// 启动 XA 模式
spring:
  cloud:
    alibaba:
      seata:
        mode: xa

// 代码与本地事务一致
@GlobalTransactional
public void placeOrder(OrderDto dto) {
    orderRepository.save(new Order(dto));
    inventoryClient.deduct(dto.skuId, dto.quantity);
}
text
XA 模式:数据库层 2PC,强一致,性能差
AT 模式:应用层 2PC,最终一致,性能好

⚠️ 坑 3:XA 模式需要数据库支持 XA 协议,MySQL 8 InnoDB 支持,部分云数据库不支持。

八、实战避坑 ​

java
// 1. Feign 调用要走 Seata 代理
@FeignClient(name = "inventory-service", configuration = FeignSeataConfig.class)
public interface InventoryClient {
    @PostMapping("/api/inventory/deduct")
    void deduct(@RequestBody DeductRequest req);
}

// 2. 异步调用要传递 XID
RestTemplate restTemplate = new RestTemplate();
// Header 注入 XID
java
// 3. 异常处理
@GlobalTransactional(noRollbackFor = BusinessException.class)
public void placeOrder(OrderDto dto) {
    // 业务异常不回滚全局事务
}

本章小结 ​

模式一致性性能复杂度
AT最终高低
TCC最终中高
Saga最终中中
XA强低中
选型建议
默认AT 模式
支付TCC
长流程Saga
强一致XA

动手练习 ​

  1. Server 部署:用 Docker 启动 Seata Server + MySQL
  2. AT 集成:写 order + inventory 两个服务,演示 Seata AT 全局提交和回滚
  3. 脏写测试:两个并发事务同时改一行,加全局锁修复
  4. TCC 实现:把订单库存 TCC 化,验证 confirm / cancel 行为

下一章:第 16 章:最终一致性 →

本站基于 VitePress 构建 · 由 StackHub 团队维护