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

举报
威哥爱编程 发表于 2024/12/16 18:41:51 2024/12/16
【摘要】 这些组件协同工作,通过二阶段提交协议来确保分布式事务的原子性和一致性。TC 作为中心节点,负责协调各个 RM 的行为,而 TM 则负责向 TC 发起全局事务的请求。

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的依赖:

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

2. 配置application.yml

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

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的数据源代理。

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

4. 业务代码实现

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

订单服务

@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);
        // 其他业务逻辑...
    }
}

库存服务

@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);
        // 其他业务逻辑...
    }
}

账户服务

@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为例:

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方法:

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

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

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

2. 实现TccAction

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

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来执行分布式事务:

@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模式:

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

5. 注册TccAction到Spring容器

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

@Configuration
public class SeataTccConfig {

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

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

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

@Service
public class BusinessService {

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

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

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

4. 使用案例 - Saga 模式

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

1. 定义Saga事务接口

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

public interface ISagaService {
    /**
     * 开始执行Saga事务
     */
    void startSaga();

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

2. 实现SagaService

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

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事务失败时执行:

@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格式的配置文件,定义了状态图的节点和边,以及正向操作和补偿操作的方法名称。

{
  "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注解来启动全局事务:

@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模式依赖:

<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数据源:

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数据源:

@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数据源:

@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 发起全局提交或回滚的请求。

最后

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。