Skip to content

09-分布式事务(Seata)

对应原始资料:高级03-分布式事务seata的部署和集成.md

一、为什么会有分布式事务

微服务架构下,一个业务操作跨多个服务、多个数据库:

下单 → 扣库存(库存服务) → 扣余额(账户服务) → 改订单状态(订单服务)

任何一个失败,都需要全部回滚,否则数据不一致。这就是分布式事务问题。

二、CAP 与 BASE 理论

CAP

  • C 一致性A 可用性P 分区容错
  • 分布式系统只能选 2 个,由于分区不可避免,实际在 CP(强一致)和 AP(高可用)间权衡。

BASE

  • Basically Available 基本可用。
  • Soft state 软状态。
  • Eventually consistent 最终一致。

互联网业务大多追求最终一致,而非强一致。

三、分布式事务常见方案

方案一致性说明
2PC(两阶段提交)性能差、阻塞
TCC(Try-Confirm-Cancel)业务侵入,需要每个操作写三套
Seata AT 模式弱强自动补偿,业务无侵入(推荐)
可靠消息最终一致最终本地消息表 + MQ
Saga最终长事务,逐步提交/补偿

四、Seata 架构

三个角色

  • TC(Transaction Coordinator):事务协调者,独立部署(seata-server)。
  • TM(Transaction Manager):事务管理器,发起全局事务的服务。
  • RM(Resource Manager):资源管理器,每个参与事务的数据库。

全局事务流程(AT 模式)

TM 调用 TC 开启全局事务(拿到 XID)
  ↓ XID 通过请求头传给各 RM
RM1 执行本地事务 + 记录 undo_log(用于回滚)→ 注册分支事务到 TC
RM2 执行本地事务 + 记录 undo_log → 注册分支事务到 TC

TM 调用 TC:提交 or 回滚
TC 通知所有 RM:提交(异步删 undo_log)/ 回滚(根据 undo_log 反向补偿)

五、Seata Server 部署

1. 准备表

每个业务库都要建 undo_log 表:

sql
CREATE TABLE undo_log (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  branch_id BIGINT, xid VARCHAR(100),
  context VARCHAR(128), rollback_info LONGBLOB,
  log_status INT, log_created DATETIME, log_modified DATETIME
);

2. 启动 seata-server

bash
docker run -d --name seata -p 8091:8091 \
  -e SEATA_IP=127.0.0.1 seataio/seata-server

(资料中有详细的 seata的部署和集成.md,包括用 Nacos 作为注册中心、DB 存储模式等)

六、SpringBoot 集成(AT 模式)

1. 依赖

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

2. 配置

yaml
seata:
  registry:
    type: nacos
    nacos:
      server-addr: localhost:8848
      group: SEATA_GROUP
      application: seata-server
  tx-service-group: my_tx_group
  service:
    vgroup-mapping:
      my_tx_group: default

3. 使用

java
@Service
public class OrderService {
    @Autowired private StorageClient storageClient;
    @Autowired private AccountClient accountClient;
    @Autowired private OrderMapper orderMapper;

    @GlobalTransactional                // 开启全局事务
    public void createOrder(Order order) {
        orderMapper.insert(order);                  // 本地:建订单
        storageClient.deduct(order.getProductId()); // 远程:扣库存
        accountClient.deduct(order.getUserId());    // 远程:扣余额
        // 任何一步抛异常,全部回滚
    }
}

七、AT 模式的"读已提交"问题

AT 模式默认隔离级别是读未提交(全局提交前,本地事务已提交)。需要更强隔离可用 @GlobalLock + SELECT FOR UPDATE。

八、TCC 模式(了解)

需要业务自定义:

java
@LocalTCC
public interface AccountTcc {
    @TwoPhaseBusinessAction(name = "deduct",
        commitMethod = "confirm", rollbackMethod = "cancel")
    boolean prepare(BusinessActionContext ctx, Long userId, BigDecimal money);

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

性能高、对一致性要求高的场景使用。

九、可靠消息最终一致(替代方案)

适合异步的长流程业务:

订单服务 → MQ → 库存服务 → MQ → 积分服务
  • 本地消息表 / RocketMQ 事务消息保证「本地事务 + 发消息」原子性。
  • 消费方幂等。

十、选型建议

场景推荐
同公司、对一致要求高、业务短Seata AT
业务极复杂、性能要求高TCC
长流程、跨系统、可容忍延迟可靠消息
老系统改造、不能改业务Saga

练习建议

  1. 启动 seata-server,注册到 Nacos。
  2. 搭建「订单-库存-账户」三个服务,建 undo_log。
  3. @GlobalTransactional 完成下单流程,故意制造一个异常观察全局回滚。
  4. 对比 AT 模式和「可靠消息最终一致」的差异。