MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Spring Cloud Alibaba 分布式事务解决方案

2023-01-056.1k 阅读

分布式事务概述

在单体应用架构中,事务处理相对简单,通过数据库自身的事务机制就能保证数据的一致性。但随着微服务架构的流行,一个业务往往会被拆分成多个微服务,不同微服务可能使用不同的数据库,此时传统的单体事务管理方式就不再适用,分布式事务问题应运而生。

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单来说,当一个业务操作涉及多个微服务,每个微服务对各自的数据库进行操作,这些操作要么全部成功,要么全部失败,这就需要分布式事务来保证数据的一致性。

分布式事务产生的场景

  1. 跨微服务操作:例如一个电商系统,下单操作可能涉及订单微服务创建订单记录,库存微服务扣减库存,支付微服务处理支付等多个微服务的操作。如果其中某个微服务操作失败,而其他微服务操作成功,就会导致数据不一致。
  2. 多数据源操作:即使在一个微服务内部,如果使用了多个不同的数据库,比如主数据库存储业务数据,从数据库存储统计数据,在对这些不同数据库进行操作时也可能产生分布式事务问题。

分布式事务的特性

分布式事务同样需要满足 ACID 特性,但在分布式环境下实现起来更为复杂。

  1. 原子性(Atomicity):整个分布式事务中的所有操作,要么全部成功,要么全部失败。在分布式环境中,由于网络等原因,很难像单体事务那样简单地实现原子性。
  2. 一致性(Consistency):事务执行前后,数据的完整性和一致性应该得到保证。分布式事务中,不同节点的数据可能存在复制和同步延迟,要确保所有节点最终数据一致是个挑战。
  3. 隔离性(Isolation):不同事务之间应该相互隔离,互不干扰。在分布式系统中,多个事务可能同时对不同节点的数据进行操作,需要合理的隔离机制来避免数据冲突。
  4. 持久性(Durability):一旦事务提交成功,对数据的修改就应该永久保存。在分布式环境中,要考虑节点故障、网络分区等情况,确保数据的持久性。

主流分布式事务解决方案

  1. 两阶段提交(2PC)
    • 原理:两阶段提交将事务的提交过程分为两个阶段,即准备阶段(Prepare)和提交阶段(Commit)。在准备阶段,事务管理器向所有参与者发送 Prepare 请求,参与者执行事务操作,但不提交。如果所有参与者都返回成功响应,事务管理器进入提交阶段,向所有参与者发送 Commit 请求,参与者正式提交事务;如果有任何一个参与者返回失败响应,事务管理器向所有参与者发送 Rollback 请求,参与者回滚事务。
    • 优点:实现相对简单,能严格保证事务的 ACID 特性。
    • 缺点:存在单点故障问题,事务管理器一旦出现故障,整个分布式事务就无法继续;同步阻塞问题,在准备阶段和提交阶段,参与者都处于阻塞状态,等待事务管理器的指令,这会影响系统的并发性能;协调成本高,涉及多次网络通信,在网络不稳定的情况下,性能会受到较大影响。
  2. 三阶段提交(3PC)
    • 原理:三阶段提交在两阶段提交的基础上增加了一个预询问(CanCommit)阶段。在预询问阶段,事务管理器向所有参与者发送 CanCommit 请求,询问参与者是否可以执行事务操作。参与者根据自身情况返回响应,如果所有参与者都返回可以执行,事务管理器进入准备阶段,否则进入中断事务流程。准备阶段和提交阶段与两阶段提交类似。
    • 优点:相比两阶段提交,部分解决了单点故障问题和同步阻塞问题。在预询问阶段,如果事务管理器故障,参与者可以根据自身情况决定是否继续等待。
    • 缺点:实现相对复杂,增加了额外的网络通信开销,而且仍然不能完全避免数据不一致的情况。
  3. TCC(Try - Confirm - Cancel)
    • 原理:TCC 将事务分为三个阶段。Try 阶段:主要是对业务系统资源进行检测和预留;Confirm 阶段:在 Try 阶段成功的前提下,对预留的资源进行确认提交;Cancel 阶段:如果 Try 阶段执行失败或者超时,对 Try 阶段预留的资源进行释放。例如在一个转账场景中,Try 阶段冻结双方账户金额,Confirm 阶段进行实际转账,Cancel 阶段解冻冻结的金额。
    • 优点:实现比较灵活,不需要依赖数据库的事务机制,适用于对性能要求较高,且业务逻辑可以支持这种补偿机制的场景。
    • 缺点:对业务侵入性强,需要业务代码实现 Try、Confirm 和 Cancel 三个方法;TCC 事务管理的复杂性较高,需要处理事务的悬挂(Try 超时,Confirm 或 Cancel 后执行)和空回滚(Try 未执行,Cancel 执行)等问题。
  4. 本地消息表
    • 原理:在发起事务的微服务本地数据库中创建一张消息表,在事务执行前,先向消息表插入一条消息记录,记录事务相关信息。事务执行完成后,将消息表中的记录状态标记为待发送。有一个专门的消息发送服务,定时扫描消息表,将待发送的消息发送给其他微服务。接收消息的微服务处理完业务后,返回处理结果。如果消息发送失败或者接收微服务处理失败,消息发送服务可以进行重试。
    • 优点:实现相对简单,基于本地数据库事务保证消息表操作和业务操作的一致性,对业务侵入性相对较小。
    • 缺点:消息表增加了数据库的复杂度;消息发送和重试机制可能导致消息的重复消费,需要接收方做好幂等性处理。
  5. 可靠消息最终一致性
    • 原理:通过消息中间件来保证事务的最终一致性。在事务发起方,当业务操作完成后,向消息中间件发送一条事务消息。消息中间件将消息持久化后,返回确认消息给事务发起方。事务发起方接收到确认消息后,提交本地事务。消息中间件负责将消息可靠地发送给事务参与方,事务参与方处理完业务后,向消息中间件返回确认。如果消息发送失败,消息中间件可以进行重试。
    • 优点:对业务侵入性较小,利用消息中间件的可靠性保证消息传递,适用于大多数分布式事务场景。
    • 缺点:依赖消息中间件的可靠性;事务参与方需要处理消息的重复消费问题,做好幂等性处理。

Spring Cloud Alibaba 分布式事务解决方案

Spring Cloud Alibaba 提供了多种分布式事务解决方案,其中 Seata 是其核心的分布式事务框架,同时也支持使用 RocketMQ 实现可靠消息最终一致性方案。

  1. Seata 分布式事务框架
    • Seata 架构:Seata 主要由三个组件组成,分别是 TC(Transaction Coordinator)事务协调器、TM(Transaction Manager)事务管理器和 RM(Resource Manager)资源管理器。
      • TC:负责维护全局事务的状态,协调全局事务的提交和回滚。它是一个独立的服务,可以部署在多个节点上,以提高可用性。
      • TM:定义全局事务的边界,例如开始一个全局事务、提交或回滚一个全局事务。在应用程序中,通常通过注解的方式使用 TM。
      • RM:管理分支事务,负责向 TC 注册分支事务,报告分支事务的状态,以及根据 TC 的指令提交或回滚分支事务。RM 通常集成在各个微服务的数据源层。
    • Seata 事务模式:Seata 支持三种事务模式,分别是 AT 模式、TCC 模式和 XA 模式。
      • AT 模式:基于支持本地 ACID 事务的关系型数据库。在 AT 模式下,RM 会自动对业务 SQL 进行解析,生成前后镜像,并将这些镜像信息和业务 SQL 一起作为分支事务的一部分,在提交或回滚时由 RM 自动处理。例如,对于一个更新操作,RM 会记录更新前和更新后的数据,在回滚时可以根据这些镜像数据恢复到更新前的状态。
      • TCC 模式:与传统的 TCC 模式类似,需要业务代码实现 Try、Confirm 和 Cancel 方法。Seata 的 TCC 模式在事务协调和管理方面提供了更方便的支持,例如事务的自动重试、悬挂和空回滚处理等。
      • XA 模式:基于数据库的 XA 协议,通过 XA 接口实现分布式事务。XA 模式对数据库的兼容性要求较高,并且性能相对较低,在实际应用中使用较少。
    • Seata AT 模式示例
      • 引入依赖:在 Spring Boot 项目的 pom.xml 文件中引入 Seata 相关依赖。
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring - cloud - starter - alibaba - seata</artifactId>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata - spring - boot - starter</artifactId>
    <version>1.4.2</version>
</dependency>
    - **配置 Seata**:在 `application.yml` 文件中配置 Seata 相关参数,包括事务组名称、TC 地址等。
seata:
  tx - service - group: my_tx_group
  service:
    vgroup - mapping:
      my_tx_group: default
  client:
    rm:
      async - commit - buffer - limit: 10000
      lock:
        retry - internal: 10
        retry - times: 30
    tm:
      commit - retry - count: 5
      rollback - retry - count: 5
    undo:
      data - validation: true
      log - snapshot: true
      log - save - days: 7
    log:
      exception - rate: 100
    - **开启全局事务**:在需要开启全局事务的方法上添加 `@GlobalTransactional` 注解。
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @GlobalTransactional
    public void createOrder() {
        // 业务逻辑,例如调用库存微服务扣减库存,创建订单记录等
    }
}
  1. 基于 RocketMQ 的可靠消息最终一致性方案
    • 原理:Spring Cloud Alibaba 整合 RocketMQ 实现可靠消息最终一致性方案。在事务发起方,当业务操作完成后,向 RocketMQ 发送一条半消息(Half Message)。RocketMQ 将半消息持久化成功后,返回确认消息给事务发起方。事务发起方接收到确认消息后,提交本地事务。RocketMQ 会定时检查半消息的状态,如果事务发起方本地事务提交成功,RocketMQ 将半消息转换为普通消息发送给事务参与方;如果事务发起方本地事务回滚,RocketMQ 删除半消息。事务参与方接收到消息后,处理业务并返回确认。
    • 示例
      • 引入依赖:在 Spring Boot 项目的 pom.xml 文件中引入 RocketMQ 相关依赖。
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring - cloud - starter - alibaba - rocketmq</artifactId>
</dependency>
    - **配置 RocketMQ**:在 `application.yml` 文件中配置 RocketMQ 的相关参数,包括 NameServer 地址、生产者和消费者的相关配置等。
rocketmq:
  name - server: 127.0.0.1:9876
  producer:
    group: my - producer - group
    send - msg - timeout: 3000
  consumer:
    group: my - consumer - group
    subscribe:
      - topic: my - topic
        tag: *
    - **发送半消息**:在事务发起方的业务代码中,发送半消息并处理事务状态。
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Transactional
    public void createOrder() {
        // 业务操作
        Message<String> message = MessageBuilder.withPayload("order created").build();
        rocketMQTemplate.sendMessageInTransaction("my - topic", message, null);
    }
}
    - **接收消息**:在事务参与方,配置 RocketMQ 消费者来接收消息并处理业务。
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

@Component
@RocketMQMessageListener(topic = "my - topic", consumerGroup = "my - consumer - group")
public class OrderConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        // 处理订单消息,例如更新库存等业务逻辑
    }
}

选择合适的分布式事务解决方案

  1. 性能要求:如果系统对性能要求极高,对数据一致性的要求可以稍微放宽到最终一致性,那么基于可靠消息最终一致性方案(如使用 RocketMQ)可能是一个不错的选择。因为这种方案在事务执行过程中,不会像 2PC 那样长时间阻塞资源,能提高系统的并发性能。而对于性能要求相对较低,但对数据一致性要求严格的场景,Seata 的 AT 模式可以提供很好的支持,它基于数据库的本地事务机制,能保证事务的 ACID 特性。
  2. 业务复杂度:如果业务逻辑相对简单,且对数据库的操作主要是常规的增删改查,Seata 的 AT 模式比较适合,它对业务代码的侵入性较小,不需要业务代码手动实现复杂的补偿逻辑。但如果业务逻辑复杂,涉及到资源的预留、确认和释放等操作,TCC 模式可能更合适,虽然它对业务侵入性强,但可以根据业务需求灵活实现事务的各个阶段。
  3. 系统架构:如果系统已经大量使用了 RocketMQ 作为消息中间件,那么基于 RocketMQ 的可靠消息最终一致性方案可以更好地与现有架构融合,减少技术栈的复杂度。如果系统对分布式事务的管理和协调有较高的要求,希望有一个统一的事务管理框架,Seata 则是一个全面的解决方案,它提供了丰富的事务模式和强大的事务管理功能。

分布式事务实践中的注意事项

  1. 幂等性处理:在分布式事务中,由于网络等原因,消息可能会重复发送,事务操作可能会重试。因此,所有的事务参与方都需要实现幂等性,即多次执行相同的操作,结果应该是一致的。例如在处理支付回调时,无论收到多少次相同的回调消息,都应该只进行一次实际的支付处理。实现幂等性可以通过在数据库中使用唯一索引、记录操作状态等方式。
  2. 事务超时处理:分布式事务涉及多个节点和网络通信,可能会出现事务执行时间过长的情况。需要合理设置事务超时时间,当事务超时时,及时进行回滚或补偿操作,以避免资源长时间被占用。同时,在设计业务逻辑时,应该尽量减少事务的执行时间,例如将一些非关键的操作放在事务外执行。
  3. 数据一致性监控:虽然分布式事务解决方案旨在保证数据的一致性,但在实际运行中,由于各种异常情况,可能会出现数据不一致的问题。因此,需要建立数据一致性监控机制,定期检查各个节点的数据状态,及时发现并处理数据不一致的情况。可以通过编写数据比对脚本、使用专门的数据监控工具等方式实现。
  4. 故障恢复:分布式系统中,节点故障、网络故障等情况不可避免。在设计分布式事务解决方案时,需要考虑故障恢复机制。例如,当某个微服务节点故障时,如何保证已执行的事务部分能够回滚,未执行的部分能够在节点恢复后继续执行。Seata 的事务协调器和资源管理器都提供了一定的故障恢复能力,但在实际应用中,还需要根据具体情况进行调整和优化。

在 Spring Cloud Alibaba 微服务架构中,分布式事务是一个关键问题。通过合理选择 Seata 或基于 RocketMQ 的可靠消息最终一致性方案,并注意实践中的各种事项,可以有效地解决分布式事务问题,保证系统的数据一致性和稳定性。在实际应用中,需要根据系统的具体需求和特点,灵活选择和组合不同的分布式事务解决方案,以达到最佳的性能和可靠性。