您现在的位置是:首页 >学无止境 >微服务之事务网站首页学无止境

微服务之事务

qq_40784183 2025-06-17 12:01:04
简介微服务之事务

       在分布式系统中,事务管理是一个复杂且关键的问题。由于服务被拆分为多个独立的微服务,传统的单机事务(如数据库事务)无法直接应用于跨服务的场景。因此,分布式事务解决方案应运而生。本文将介绍几种主流的分布式事务方案,包括 Seata2PCTCCSaga 等.


1.  分布式事务的挑战

在分布式系统中,事务需要跨越多个服务、数据库和消息队列,面临以下挑战:

  1. 数据一致性:如何保证多个服务的数据在事务提交后保持一致。

  2. 性能开销:分布式事务通常需要额外的协调和通信,可能影响系统性能。

  3. 复杂性:实现分布式事务需要处理网络分区、服务宕机等异常情况。


2.  主流分布式事务方案

2.1. Seata(阿里巴巴开源)

Seata 是一款开源的分布式事务解决方案,支持 AT(自动补偿)、TCC、Saga 和 XA 模式。

方案说明
  • AT 模式:基于两阶段提交(2PC)的改进版本,通过全局锁和本地事务实现数据一致性。

  • TCC 模式:通过 Try、Confirm、Cancel 三个阶段实现事务的最终一致性。

  • Saga 模式:通过长事务和补偿机制实现最终一致性。

  • XA 模式:基于 XA 协议的两阶段提交。

优点
  • 多模式支持:支持多种分布式事务模式,适应不同场景。

  • 易用性:提供简单的 API 和注解,易于集成到 Spring Cloud 等框架。

  • 高性能:AT 模式通过本地事务和全局锁优化性能。

  • 社区活跃:作为阿里巴巴开源项目,Seata 拥有活跃的社区和丰富的文档。

缺点
  • 全局锁性能瓶颈:在高并发场景下,全局锁可能成为性能瓶颈。

  • 复杂性:TCC 和 Saga 模式需要开发者手动实现补偿逻辑。

适用场景
  • 需要高一致性的金融、电商等场景。

  • 对性能要求较高的场景(AT 模式)。

  • 需要灵活选择事务模式的场景。


2.2.  2PC(两阶段提交)

2PC 是一种经典的分布式事务协议,分为准备阶段和提交阶段。

方案说明
  • 准备阶段:协调者询问所有参与者是否可以提交事务。

  • 提交阶段:如果所有参与者都同意提交,协调者通知参与者提交事务;否则回滚。

优点
  • 强一致性:保证所有参与者要么全部提交,要么全部回滚。

  • 简单易懂:协议逻辑清晰,易于理解。

缺点
  • 性能差:同步阻塞,事务执行时间长。

  • 单点故障:协调者宕机可能导致事务阻塞。

  • 数据不一致风险:在提交阶段,如果部分参与者提交失败,可能导致数据不一致。

适用场景
  • 对一致性要求极高的场景。

  • 事务参与者较少的场景。


2.3. TCC(Try-Confirm-Cancel)

TCC 是一种基于补偿机制的分布式事务方案。

方案说明
  • Try 阶段:预留资源,执行业务检查。

  • Confirm 阶段:提交事务,确认资源。

  • Cancel 阶段:回滚事务,释放资源。

优点
  • 高性能:通过资源预留减少锁冲突。

  • 最终一致性:适合高并发场景。

缺点
  • 开发复杂:需要手动实现 Try、Confirm、Cancel 逻辑。

  • 业务侵入性强:需要修改业务代码。

适用场景
  • 高并发、对性能要求较高的场景。

  • 需要最终一致性的场景。


2.4. Saga

Saga 是一种长事务解决方案,通过事件驱动和补偿机制实现最终一致性。

方案说明
  • 正向事务:依次执行多个子事务。

  • 补偿事务:如果某个子事务失败,依次执行补偿事务回滚。

优点
  • 松耦合:通过事件驱动实现服务解耦。

  • 适合长事务:支持长时间运行的事务。

缺点
  • 数据不一致风险:在补偿事务执行期间,数据可能处于不一致状态。

  • 开发复杂:需要设计正向事务和补偿事务。

适用场景
  • 长事务场景(如订单、物流系统)。

  • 对最终一致性要求较高的场景。


2.5. 消息队列(最终一致性)

通过消息队列实现分布式事务的最终一致性。

方案说明
  • 本地事务:在本地事务中发送消息到消息队列。

  • 消息消费:消费者消费消息并执行业务逻辑。

优点
  • 高性能:异步执行,减少事务阻塞。

  • 松耦合:通过消息队列解耦服务。

缺点
  • 数据不一致风险:消息可能丢失或重复消费。

  • 开发复杂:需要处理消息的可靠性和幂等性。

适用场景
  • 对一致性要求不高的场景。

  • 异步处理的场景(如通知、日志)。


3. Seata 的使用

3.1. 部署 Seata Server

  • 下载 Seata Server 并解压。

  • 修改 conf/registry.conf 文件,配置数据库.

  • file:(默认)单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)
    
    db:(5.7+)高可用模式,全局事务会话信息通过db共享,相应性能差些
    # 配置mysql数据库
    store {
    
      mode = "db"
    
    
      file {
        ## store location dir
        dir = "sessionStore"
      }
      db {
    
        datasource = "dbcp"
        db-type = "mysql"
        driver-class-name = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://101.42.40.100:3306/seata"
        user = "root"
        password = "root"
      }
  • conf/registry.conf 配置 nacos

 type = "nacos"

  nacos {
    serverAddr = "localhost:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
  • 新建表
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `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,
    PRIMARY KEY (`xid`),
    KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
    ) ENGINE = InnoDB
    DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `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(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
    ) ENGINE = InnoDB
    DEFAULT CHARSET = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid` (`xid`)
    ) ENGINE = InnoDB
    DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
    ) ENGINE = InnoDB
    DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
  • 配置注册
  • 启动 Seata Server:sh bin/seata-server.sh

3.2. 集成 Seata Client

  • 在 Spring Boot 项目中添加 Seata 依赖:

    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.5.0</version>
    </dependency>
  • 添加undo_log表
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
  • 配置 Seata:

    # 配置事务组
    seata:
      enabled: true
      application-id: your-app-id
      tx-service-group: my_tx_group
      registry:
        type: nacos
        nacos:
          server-addr: localhost:8848
      config:
        type: nacos
        nacos:
          server-addr: localhost:8848

3.3. 使用 Seata AT 模式

  • 在业务方法上添加 @GlobalTransactional 注解:

    @GlobalTransactional
    public void createOrder() {
        // 业务逻辑
    }

3.4. 使用 Seata TCC 模式

  • 定义 TCC 接口:

    public interface TccService {
        @TwoPhaseBusinessAction(name = "tryAction", commitMethod = "confirm", rollbackMethod = "cancel")
        boolean tryAction(BusinessActionContext context);
    
        boolean confirm(BusinessActionContext context);
    
        boolean cancel(BusinessActionContext context);
    }


   3.5.  选型建议

  1. 强一致性场景:选择 Seata AT 模式 或 2PC

  2. 高并发场景:选择 TCC 或 消息队列

  3. 长事务场景:选择 Saga

  4. 简单易用:选择 Seata,支持多种模式且易于集成。


3.6.总结

分布式事务的选型需要根据业务场景、一致性要求和性能需求来决定。Seata 作为一款功能强大且易用的分布式事务解决方案,适合大多数场景。对于特定场景,可以选择 TCC、Saga 或消息队列等方案。无论选择哪种方案,都需要结合业务特点进行合理设计和优化,以确保系统的稳定性和性能。

4. 使用笔记

4.1. seata 的三个角色

  • 事务协调者 TC 维护全局和分支事务的状态,驱动全局事务提交或者回滚
  • 事务管理器 TM 定义全局事务范围:开始全局事务,提交或者回滚全局事务
  • 资源管理器 RM 管理分支事务的资源,与TC交谈,注册分支事务 和报告分支事务状态,驱动分支事务提交或者回滚

4.2. 模式比较

4.3. Seata AT 模式的执行流程

Seata AT 模式的核心思想是通过 两阶段提交(2PC) 来实现分布式事务的一致性。它的执行流程分为以下几个阶段:

1. 事务发起阶段

  1. 全局事务开始

    • 事务发起者(通常是业务服务的入口)在方法上添加 @GlobalTransactional 注解,标记这是一个全局事务。

    • Seata 框架会为该事务生成一个全局唯一的 XID(全局事务 ID),并将 XID 传播到所有参与事务的服务。

  2. 注册分支事务

    • 每个参与事务的服务(称为 RM,Resource Manager)在执行本地事务之前,会向 Seata Server(TC,Transaction Coordinator)注册一个 分支事务

    • 分支事务的注册信息包括 XID、资源 ID(如数据源)、分支事务类型(如 AT、TCC)等。


2. 第一阶段:执行本地事务

  1. 执行业务 SQL

    • 每个参与事务的服务执行自己的业务逻辑,包括对数据库的增删改操作。

    • Seata 会通过 数据源代理 拦截这些 SQL 操作,记录 前置镜像(Before Image) 和 后置镜像(After Image),用于后续的回滚操作。

  2. 提交本地事务

    • 每个服务在完成本地事务后,会向 Seata Server 报告本地事务的状态(成功或失败)。

    • 如果本地事务成功,Seata 会将该分支事务的状态标记为 已提交但未完成,等待全局事务的最终提交。


3. 第二阶段:全局事务提交或回滚

  1. 全局事务提交

    • 如果所有分支事务都成功提交,Seata Server 会向所有分支事务发送 提交请求

    • 每个分支事务在收到提交请求后,会删除之前记录的前置镜像和后置镜像,完成事务的最终提交。

  2. 全局事务回滚

    • 如果任何一个分支事务失败,Seata Server 会向所有分支事务发送 回滚请求

    • 每个分支事务在收到回滚请求后,会根据之前记录的前置镜像和后置镜像,执行 反向 SQL,将数据恢复到事务开始之前的状态。

5. seata使用问题解决

5.1. Seata Server 启动失败

问题描述

在启动 Seata Server 时,可能会遇到以下错误:

  • 端口被占用。

  • 依赖的注册中心(如 Nacos)未启动或配置错误。

  • 数据库连接失败。

解决方案

  1. 端口被占用

    • 检查 Seata Server 的默认端口(默认是 8091)是否被占用。

    • 修改 conf/registry.conf 中的端口配置,或者停止占用端口的进程。

  2. 注册中心未启动

    • 确保注册中心(如 Nacos、Zookeeper)已正确启动。

    • 检查 conf/registry.conf 中的注册中心配置是否正确。例如:

      registry {
        type = "nacos"
        nacos {
          serverAddr = "localhost:8848"
          namespace = ""
          cluster = "default"
        }
      }

  3. 数据库连接失败

    • Seata Server 需要连接数据库来存储事务日志。确保 conf/file.conf 中的数据库配置正确:

      store {
        mode = "db"
        db {
          url = "jdbc:mysql://localhost:3306/seata"
          user = "root"
          password = "root"
        }
      }

    • 确保数据库已创建,并且 Seata 所需的表已初始化(表结构在 Seata 的 script/server/db 目录下)。


5.2. 客户端无法连接到 Seata Server

问题描述

客户端启动时,报错提示无法连接到 Seata Server,例如:

io.seata.core.exception.RmTransactionException: can not register RM,err:can not connect to services-server

解决方案

  1. 检查 Seata Server 地址

    • 确保客户端配置的 Seata Server 地址正确。在 application.yml 中配置:

      seata:
        tx-service-group: my_tx_group
        registry:
          type: nacos
          nacos:
            server-addr: localhost:8848

  2. 检查注册中心

    • 确保 Seata Server 和客户端使用相同的注册中心(如 Nacos)。

    • 登录注册中心的管理界面,检查 Seata Server 是否已成功注册。

  3. 网络问题

    • 确保客户端和 Seata Server 之间的网络是通的,没有防火墙或安全组限制。


5.3. 事务未生效

问题描述

在业务方法上添加了 @GlobalTransactional 注解,但事务未生效,例如:

  • 部分服务未回滚。

  • 事务未提交。

解决方案

  1. 检查注解是否正确

    • 确保在业务方法上正确使用了 @GlobalTransactional 注解:

      @GlobalTransactional
      public void createOrder() {
          // 业务逻辑
      }

  2. 检查数据源代理

    • Seata 需要通过代理数据源来管理事务。确保数据源被 Seata 正确代理:

      seata:
        enabled: true
        application-id: your-app-id
        tx-service-group: my_tx_group
  3. 检查分支事务注册

    • 确保每个参与分布式事务的服务都正确注册了分支事务。可以通过 Seata 的日志查看是否有分支事务注册失败。

  4. 检查全局事务 ID

    • 在 Seata Server 的日志中查找全局事务 ID(XID),检查事务的执行状态。


5.4. 数据源代理失败

问题描述

在集成 Seata 时,数据源代理失败,导致事务无法管理。

解决方案

  1. 检查数据源配置

    • 确保数据源被 Seata 的 DataSourceProxy 代理。例如:

      @Bean
      public DataSource dataSource(DataSource druidDataSource) {
          return new DataSourceProxy(druidDataSource);
      }

  2. 排除冲突依赖

    • 如果项目中使用了多个数据源或连接池(如 Druid、HikariCP),确保它们的版本兼容,并且没有冲突。

  3. 检查 Seata 版本

    • 确保 Seata 的版本与 Spring Boot、Spring Cloud 的版本兼容。


5.5. 事务回滚失败

问题描述

在分布式事务中,部分服务回滚失败,导致数据不一致。

解决方案

  1. 检查回滚逻辑

    • 确保每个服务的回滚逻辑正确实现。例如,在 TCC 模式中,确保 cancel 方法能够正确回滚资源。

  2. 检查全局锁

    • 在 AT 模式中,Seata 使用全局锁来保证数据一致性。如果全局锁未正确释放,可能导致回滚失败。检查 Seata Server 的日志,查看是否有锁冲突。

  3. 检查异常传播

    • 确保业务方法中的异常能够正确传播到 Seata 框架。例如,在 Spring 中,默认只有 RuntimeException 会触发回滚。


5.6. 性能问题

问题描述

在高并发场景下,Seata 的性能可能成为瓶颈,例如:

  • 全局锁竞争激烈。

  • 事务执行时间过长。

解决方案

  1. 优化全局锁

    • 减少全局锁的持有时间,尽量将锁粒度细化。

    • 使用 TCC 模式代替 AT 模式,减少锁冲突。

  2. 异步化处理

    • 将非核心业务逻辑异步化,减少事务的执行时间。

  3. 调整 Seata 配置

    • 根据业务场景调整 Seata 的线程池和连接池配置。


5.7. Seata 日志问题

问题描述

Seata 的日志输出过多或过少,难以排查问题。

解决方案

  1. 调整日志级别

    • 在 logback.xml 或 log4j2.xml 中调整 Seata 的日志级别:

      <logger name="io.seata" level="DEBUG" />

  2. 查看 Seata Server 日志

    • Seata Server 的日志位于 logs/seata/ 目录下,可以通过日志排查问题。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。