分布式事务Seata的4种模式详解

Seata 是一个开源的分布式事务解决方案,它在微服务架构下提供了高性能和简单易用的分布式事务服务。Seata 的设计基于 AT、TCC、Saga 和 XA 事务模式,以满足不同场景下的分布式事务处理需求,今天的内容针对 Seata 来详细介绍一下。

1、四种事务模式介绍

1. AT 模式:这是一种无侵入的分布式事务解决方案。用户只需关注自己的业务 SQL,Seata 框架会自动生成事务的二阶段提交和回滚操作。在一阶段,Seata 会拦截业务 SQL,解析 SQL 语义,找到要更新的业务数据,并保存快照数据和行锁。二阶段如果是提交,Seata 只需清理数据;如果是回滚,则用快照数据还原业务数据 。

2. TCC 模式:TCC(Try-Confirm-Cancel)模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作。Try 阶段是资源的检测和预留;Confirm 阶段执行业务操作提交;Cancel 阶段是预留资源释放 。

3. Saga 模式:Saga 模式适用于长事务,由事件驱动,各个参与者之间异步执行。如果任何一个正向操作执行失败,Saga 模式会执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态 。

4. XA 模式:XA 模式是 Seata 支持的另一种分布式事务解决方案,它利用事务资源对 XA 协议的支持,以 XA 协议的机制来管理分支事务。XA 模式是两阶段提交协议,通过资源管理器和事务协调者来保证事务的原子性。

下面我们用使用案例的方式来分别介绍4种模式,让你更好的理解不同模式下的作用。

2、使用案例-AT模式

实现一个分布式事务的案例涉及到多个微服务之间的协同工作,以及与Seata服务器的交互。以下是使用Seata的AT模式的一个示例,包括订单服务、库存服务和账户服务的基本实现,跟 V 哥来一起看一下实现过程。

1. 添加依赖

首先,在每个微服务的pom.xml文件中添加Seata的依赖:

xml 复制代码
<dependencies>
    <!-- 其他依赖... -->
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.5.2</version>
    </dependency>
</dependencies>

2. 配置application.yml

在每个微服务中配置Seata相关参数:

yaml 复制代码
server:
  port: 8081 # 每个服务的端口不同

spring:
  application:
    name: your-service-name
  datasource:
    # 数据源配置,如url, username, password等

seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: your-namespace
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: your-namespace
  service:
    vgroup-mapping:
      your-service-group: default
    # 其他配置...

3. 启动类配置

在每个微服务的启动类上添加@EnableAutoDataSourceProxy注解,以开启Seata的数据源代理。

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoDataSourceProxy
public class YourServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(YourServiceApplication.class, args);
    }
}

4. 业务代码实现

以下是每个服务中可能的业务代码实现:

订单服务
java 复制代码
@Service
public class OrderService {
    @Resource
    private OrderMapper orderMapper; // JPA或MyBatis的Mapper

    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public void createOrder(Order order) {
        // 创建订单
        orderMapper.insert(order);
        // 其他业务逻辑...
    }
}
库存服务
java 复制代码
@Service
public class StorageService {
    @Resource
    private StorageMapper storageMapper;

    @GlobalTransactional(name = "deduct-inventory", rollbackFor = Exception.class)
    public void deductInventory(Long productId, Integer count) {
        // 扣减库存
        storageMapper.deduct(productId, count);
        // 其他业务逻辑...
    }
}
账户服务
java 复制代码
@Service
public class AccountService {
    @Resource
    private AccountMapper accountMapper;

    @GlobalTransactional(name = "deduct-balance", rollbackFor = Exception.class)
    public void deductBalance(Long userId, BigDecimal money) {
        // 扣减账户余额
        accountMapper.deduct(userId, money);
        // 其他业务逻辑...
    }
}

5. 数据访问层

Mapper接口和XML文件或注解根据实际使用的ORM框架进行编写,这里以MyBatis为例:

java 复制代码
public interface OrderMapper {
    void insert(Order order);
    // 其他方法...
}

public interface StorageMapper {
    void deduct(Long productId, Integer count);
    // 其他方法...
}

public interface AccountMapper {
    void deduct(Long userId, BigDecimal money);
    // 其他方法...
}

6. 异常处理

在业务方法中,如果抛出异常,则Seata会自动进行全局事务回滚。

注意一下哈,我们忽略了服务之间的网络通信、数据库配置、事务协调者(TC)的部署和配置等因素,在实际生产中你需要考虑得更加周全一些。

3、使用案例 - TCC 模式

使用Seata的TCC(Try-Confirm-Cancel)模式,我们需要为每个分支事务实现三个方法:Try、Confirm 和 Cancel。以下是使用TCC模式的一个简化示例,包括定义TCC接口和实现类。

1. 定义TCC接口

首先定义一个TCC接口,声明Try、Confirm和Cancel方法:

java 复制代码
public interface ITccTransaction {
    /**
     * 尝试执行业务操作,预留必需的业务资源
     */
    boolean prepare();

    /**
     * 确认执行业务操作,正式提交Try阶段预留的业务资源
     */
    void commit();

    /**
     * 取消执行业务操作,释放Try阶段预留的业务资源
     */
    void cancel();
}

2. 实现TccAction

创建一个实现ITccTransaction接口的类TccAction,实现具体的业务逻辑:

java 复制代码
public class TccAction implements ITccTransaction {

    @Override
    public boolean prepare() {
        // 尝试执行业务操作,例如检查资源是否足够
        // 返回true表示Try阶段成功,false表示失败
        return true;
    }

    @Override
    public void commit() {
        // 确认执行业务操作,例如提交订单
        // 此方法在所有Try操作成功后被调用
    }

    @Override
    public void cancel() {
        // 取消执行业务操作,例如回滚订单
        // 此方法在Try阶段任一操作失败时被调用
    }
}

3. 业务服务类使用TccAction

在业务服务类中,使用TccAction来执行分布式事务:

java 复制代码
@Service
public class BusinessService {

    private final TccAction tccAction = new TccAction();

    public void executeBusiness() {
        // 执行TCC事务
        try {
            if (tccAction.prepare()) {
                // Try阶段成功,执行Confirm
                tccAction.commit();
            } else {
                // Try阶段失败,执行Cancel
                tccAction.cancel();
            }
        } catch (Exception e) {
            // 异常处理,可能需要调用Cancel
            tccAction.cancel();
            throw e;
        }
    }
}

4. 配置Seata TCC模式

在每个微服务的配置文件application.yml中配置Seata TCC模式:

yaml 复制代码
seata:
  enabled: true
  tcc:
    # 配置TCC模式的事务管理器Bean名称
    manager: tccAction

5. 注册TccAction到Spring容器

在Spring的配置类中注册TccAction到Spring容器:

java 复制代码
@Configuration
public class SeataTccConfig {

    @Bean
    public ITccTransaction tccAction() {
        return new TccAction();
    }
}

6. 使用注解启动全局事务

在业务方法上使用@GlobalTransactional注解来启动全局事务:

java 复制代码
@Service
public class BusinessService {

    // ... 其他代码 ...

    @GlobalTransactional
    public void executeBusinessInGlobalTransaction() {
        executeBusiness();
    }
}

提醒一下哈,在实际部署时,还需要考虑服务之间的网络通信、数据库配置、事务协调者(TC)的部署和配置等因素。在TCC模式下,业务侵入性较强,需要为每个分支事务手动编写Try、Confirm和Cancel逻辑。

4. 使用案例 - Saga 模式

Seata的Saga模式是用于处理长事务的解决方案,适用于业务流程长且需要保证事务最终一致性的场景。Saga模式通过状态机引擎来实现服务的编排,允许定义一系列的正向操作(执行业务逻辑)和相应的补偿操作(回滚业务逻辑)。以下是使用Seata的Saga模式的一个简化示例:

1. 定义Saga事务接口

首先定义一个Saga事务接口,声明开始事务和结束事务的方法:

java 复制代码
public interface ISagaService {
    /**
     * 开始执行Saga事务
     */
    void startSaga();

    /**
     * 结束执行Saga事务
     */
    void endSaga();
}

2. 实现SagaService

创建一个实现ISagaService接口的类SagaService,实现具体的业务逻辑:

java 复制代码
public class SagaService implements ISagaService {
    private final OrderService orderService;
    private final StorageService storageService;
    private final AccountService accountService;

    @Autowired
    public SagaService(OrderService orderService, StorageService storageService, AccountService accountService) {
        this.orderService = orderService;
        this.storageService = storageService;
        this.accountService = accountService;
    }

    @Override
    public void startSaga() {
        // 执行订单服务
        orderService.createOrder();
        // 执行库存服务
        storageService.deductInventory();
        // 执行账户服务
        accountService.deductBalance();
    }

    @Override
    public void endSaga() {
        // 这里可以放置Saga事务结束时需要执行的逻辑
    }
}

3. 定义补偿方法

为每个服务定义补偿方法,以便在Saga事务失败时执行:

java 复制代码
@Service
public class OrderService {
    // ... 省略其他代码 ...

    public void createOrder() {
        // 创建订单逻辑
    }

    public void compensateCreateOrder() {
        // 订单创建失败时的补偿逻辑
    }
    // ... 省略其他代码 ...
}

@Service
public class StorageService {
    // ... 省略其他代码 ...

    public void deductInventory() {
        // 扣减库存逻辑
    }

    public void compensateDeductInventory() {
        // 扣减库存失败时的补偿逻辑
    }
    // ... 省略其他代码 ...
}

@Service
public class AccountService {
    // ... 省略其他代码 ...

    public void deductBalance() {
        // 扣减账户余额逻辑
    }

    public void compensateDeductBalance() {
        // 扣减余额失败时的补偿逻辑
    }
    // ... 省略其他代码 ...
}

4. 配置Saga状态机

使用Seata提供的状态机定义Saga事务的状态图。这通常是一个JSON格式的配置文件,定义了状态图的节点和边,以及正向操作和补偿操作的方法名称。

json 复制代码
{
  "stateMachineDefinition": {
    "_comment": "This is a saga state machine definition",
    "id": "your-saga-id",
    "states": [
      {
        "id": "orderServiceState",
        "type": "regular",
        "serviceName": "orderService",
        "methods": {
          "regular": "createOrder",
          "compensate": "compensateCreateOrder"
        }
      },
      {
        "id": "storageServiceState",
        "type": "regular",
        "serviceName": "storageService",
        "methods": {
          "regular": "deductInventory",
          "compensate": "compensateDeductInventory"
        }
      },
      {
        "id": "accountServiceState",
        "type": "regular",
        "serviceName": "accountService",
        "methods": {
          "regular": "deductBalance",
          "compensate": "compensateDeductBalance"
        }
      }
    ],
    "links": [
      {
        "from": "orderServiceState",
        "to": "storageServiceState"
      },
      {
        "from": "storageServiceState",
        "to": "accountServiceState"
      }
    ],
    "end": "accountServiceState"
  }
}

5. 使用注解启动全局事务

SagaServicestartSaga方法上使用@GlobalTransactional注解来启动全局事务:

java 复制代码
@Service
public class SagaService implements ISagaService {
    // ... 其他代码 ...

    @GlobalTransactional(name = "saga-transaction", rollbackFor = Exception.class)
    @Override
    public void startSaga() {
        // Saga事务逻辑
    }
}

Saga模式允许业务流程中的每个参与者提交本地事务,并通过事件驱动的异步执行来保证事务的最终一致性。

5、使用案例 - XA 模式

Seata的XA模式是Seata提供的对XA协议的支持,它允许使用具有XA能力的数据库来参与分布式事务。下面来看一下使用Seata的XA模式的一个示例:

1. 添加依赖

在微服务的pom.xml文件中添加Seata的XA模式依赖:

xml 复制代码
<dependencies>
    <!-- 其他依赖... -->
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.5.2</version>
    </dependency>
    <!-- 添加XA数据源依赖,以HikariCP为例 -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.0.0</version>
    </dependency>
</dependencies>

2. 配置application.yml

在微服务的application.yml文件中配置Seata和XA数据源:

yaml 复制代码
server:
  port: 8081

spring:
  application:
    name: your-service-name

seata:
  enabled: true
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: your-namespace
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: your-namespace
  tx-service-group: your-tx-service-group

# XA数据源配置
xa:
  datasource:
    ds1: 
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/your_database1
      username: your_username
      password: your_password
    ds2: 
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/your_database2
      username: your_username
      password: your_password

3. 配置XA数据源

创建一个配置类来配置XA数据源:

java 复制代码
@Configuration
public class XaDataSourceConfig {

    @Value("${xa.datasource.ds1}")
    private String ds1;
    @Value("${xa.datasource.ds2}")
    private String ds2;

    @Bean
    public XADataSource dataSource1() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(ds1);
        // 其他配置...
        return new XADataSourceProxy(dataSource);
    }

    @Bean
    public XADataSource dataSource2() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(ds2);
        // 其他配置...
        return new XADataSourceProxy(dataSource);
    }
}

4. 使用XA数据源

在业务服务中使用XA数据源:

java 复制代码
@Service
public class XaBusinessService {

    @Autowired
    private JdbcTemplate jdbcTemplate1;
    @Autowired
    private JdbcTemplate jdbcTemplate2;

    @Transactional
    public void performBusiness() {
        jdbcTemplate1.execute("BEGIN");
        // 执行数据库操作1
        jdbcTemplate1.update("UPDATE account SET balance = balance - ? WHERE id = ?", 100, 1);
        
        jdbcTemplate2.execute("BEGIN");
        // 执行数据库操作2
        jdbcTemplate2.update("UPDATE inventory SET quantity = quantity - ? WHERE product_id = ?", 10, 1);
        
        // 如果所有操作都成功,则提交事务
        jdbcTemplate1.commit();
        jdbcTemplate2.commit();
    }
}

XA模式通过两阶段提交(2PC)来保证事务的原子性,适用于需要强一致性的场景。

5、Seata 的核心组件

Seata 的核心组件主要包括以下三个部分:

1.Transaction Coordinator (TC) - 事务协调者

负责维护全局事务的运行状态,是全局事务的大脑。TC 负责协调和管理所有的全局与分支事务,驱动全局事务的提交或回滚。

  • 角色定位:TC 在分布式事务中扮演着"协调者"的角色,它是全局事务的中心节点,负责协调和管理所有分支事务的生命周期。
  • 主要职责
    • 维护全局事务和分支事务的状态信息。
    • 驱动全局事务的提交或回滚操作。
    • 收集所有分支事务的执行情况,并根据这些信息决定全局事务是提交还是回滚。
    • 与各个 Resource Manager (RM) 通信,调度分支事务的提交或回滚。
  • 工作机制:TC 通过生成全局唯一的 XID(Transaction ID)来标识每个全局事务,并在全局事务的两阶段提交过程中,负责与所有的 RM 进行通信和协调。

2. Resource Manager (RM) - 资源管理器

负责管理本地事务处理的资源。RM 与 TC 进行通信,执行 TC 指令来保证本地事务的一致性。RM 主要是指事务的参与者,例如数据库连接,它们向 TC 注册分支事务,并汇报本地事务的状态,接收 TC 的命令来驱动本地事务的提交或回滚。

  • 角色定位:RM 是分布式事务中"资源的管理者",它负责管理本地资源,如数据库连接、消息队列等。
  • 主要职责
    • 管理本地事务的资源,例如数据库连接和事务。
    • 向 TC 注册本地事务,并汇报本地事务的状态。
    • 接收 TC 的指令来驱动本地事务的提交或回滚。
    • 在两阶段提交的第一阶段,RM 负责准备本地事务,并在第二阶段根据 TC 的指令执行提交或回滚操作。
  • 工作机制:RM 在全局事务中作为分支事务的参与者,负责具体的业务执行和资源锁定,确保本地事务的原子性和一致性。

3. Transaction Manager ™ - 事务管理器

负责全局事务的启动、提交和回滚。TM 是全局事务的发起方,控制全局事务的范围,与 TC 和 RM 进行交互,并根据 TC 维护的全局事务和分支事务状态,做出全局提交或回滚的决议。

  • 角色定位:TM 是分布式事务的"发起者",它负责定义全局事务的范围和边界。
  • 主要职责
    • 启动、提交或回滚全局事务。
    • 向 TC 发送全局事务的开始请求,并在业务逻辑完成后发起全局提交或回滚的决议。
    • 维护与 TC 的通信,传递业务逻辑的执行结果和全局事务的决议。
  • 工作机制:TM 在应用层面上定义了全局事务的开始和结束,它在业务逻辑开始时向 TC 注册全局事务,并在业务逻辑结束时根据执行结果向 TC 发起全局提交或回滚的请求。

Seata 是一个开源的分布式事务解决方案,它通过三个核心组件协同工作来提供高性能和简单易用的分布式事务服务。以下是对这三个核心组件的详细介绍和解释:

最后

这些组件协同工作,通过二阶段提交协议来确保分布式事务的原子性和一致性。TC 作为中心节点,负责协调各个 RM 的行为,而 TM 则负责向 TC 发起全局事务的请求。RM 则负责具体的资源管理,如数据库连接,并根据 TC 的指令执行相应的操作。通过这些组件和相应的协议,Seata 能够提供分布式事务的管理和协调能力,帮助开发者简化分布式系统中的事务处理,并确保数据的一致性。关注威哥爱编程,一起在技术路上成长。

相关推荐
阿伟*rui7 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
P.H. Infinity4 小时前
【RabbitMQ】03-交换机
分布式·rabbitmq
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7895 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet