分布式事务

分布式事务

为什么 会出现分布式事务?

  • 在早期单体架构中,不同的Service操作数据库的过程中,某一个操作失败,可以使用@Transactional事务注解进行数据库的回滚操作
  • 但在微服务架构中,不同的操作可能存在于不同的微服务中,他们使用不同的Tamcat,某一个操作失败,就会造成事务的不一致

定义:

  • 在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。
  • 其中的每一个事务就是一个分支事务
  • 整个业务称为全局事务

初识Seata

Seata是2019年1月蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案

Seata分布式事务解决思路

解决分布式事务,各个子事务之间必须能感知彼此的事务状态,才能保持状态一致

Seata架构

Seata事务管理中有三个重要的角色

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

TC服务的部署

数据库部署

因为TC需要记录每一个分支事务的状态,因此建议持久化保存

CREATE DATABASE IF NOT EXISTS `seata`;
USE `seata`;


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;


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;


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_and_branch_id` (`xid`, `branch_id`)
) 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);

docker部署

加载seata.tar包镜像

下载地址seata-1.5.2.tar

docker load -i seata.tar

修改seata``application.yml文件配置

配置文件

server:
  port: 7099 # 运行端口

spring:
  application:
    name: seata-server #加入到nocos中的服务名称

console:
  user:
    username: admin
    password: admin
seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos #配置读取nacos配置
    nacos:
      server-addr: hmall-nocos:8848 #nacos地址
      group: 'DEFAULT_GROUP' #nacos用户组
      namespace: ''# nacos命名空间
      dataId: 'share-seata.yaml'# nacos配置文件名
      username: 'nacos'# nacos用户名
      password: 'nacos'# nacos密码
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos # nacos服务 seata本质上属于一个微服务 因此交给nacos管理
    nacos:
      application: seata-server #nacos服务名
      server-addr: hmall-nocos:8848 #nacos地址
      group: 'DEFAULT_GROUP' #nacos用户组
      namespace: ''# nacos命名空间
      username: 'nacos' # nacos用户名
      password: 'nacos'# nacos密码
  #  server:
  #    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security: #安全认证配置 用于确保服务之间的通信安全
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 #安全认证的一个配置项 用于生成和验证 JWT(JSON Web Token)令牌
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

nacos配置

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  # extend:
  #   logstash-appender:
  #     destination: 127.0.0.1:4560
  #   kafka-appender:
  #     bootstrap-servers: 127.0.0.1:9092
  #     topic: logback_to_logstash

seata:
  #  server:
  #    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  server:
    # service-port: 8091 #If not configured, the default is '${server.port} + 1000'
    max-commit-retry-timeout: -1
    max-rollback-retry-timeout: -1
    rollback-retry-timeout-unlock-enable: false
    enable-check-auth: true
    enable-parallel-request-handle: true
    retry-dead-threshold: 130000
    xaer-nota-retry-timeout: 60000
    enableParallelRequestHandle: true
    recovery:
      committing-retry-period: 1000
      async-committing-retry-period: 1000
      rollbacking-retry-period: 1000
      timeout-retry-period: 1000
    undo:
      log-save-days: 7
      log-delete-period: 86400000
    session:
      branch-async-queue-size: 5000 #branch async remove queue size
      enable-branch-async-remove: false #enable to asynchronous remove branchSession
  store:
    # support: file 、 db 、 redis
    mode: db #使用的持久化模式 db数据库
    session:
      mode: db #使用的持久化模式 db数据库
    lock:
      mode: db #使用的锁模式 db数据库
    db:
      datasource: druid #数据连接池 druid阿里巴巴开源的高性能数据库连接池
      db-type: mysql #数据库类型
      driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动的类名
      url: jdbc:mysql://hmall-data:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC #数据库地址
      user: root # 数据库用户名
      password: 123 # 数据库密码
      min-conn: 10 #数据库连接池的最小连接数
      max-conn: 100 #数据库连接池的最大连接数
      global-table: global_table #全局事务表的名称
      branch-table: branch_table #分支事务表的名称
      lock-table: lock_table #锁表的名称
      distributed-lock-table: distributed_lock #分布式锁表的名称
      query-limit: 1000 #查询结果集的最大返回行数限制
      max-wait: 5000 #获取数据库连接的最大等待时间
    # redis:
    #   mode: single
    #   database: 0
    #   min-conn: 10
    #   max-conn: 100
    #   password:
    #   max-total: 100
    #   query-limit: 1000
    #   single:
    #     host: 192.168.150.101
    #     port: 6379
  metrics:
    enabled: false
    registry-type: compact
    exporter-list: prometheus
    exporter-prometheus-port: 9898
  transport:
    rpc-tc-request-timeout: 15000
    enable-tc-server-batch-send-response: false
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      boss-thread-size: 1

编写docker-compose.yml文件 运行到容器中

hmall-seata: # 服务名称(创建的容器名)
  image: seataio/seata-server:1.5.2 # 镜像名称
  container_name: hmall-seata # 容器名称
  ports:
    - '8099:8099'
    - '7099:7099'
  environment: #添加环境变量
    - SEATA_IP=172.29.208.1 # Seata服务端IP
  volumes: # 挂载卷
    - '../hmSeata:/seata-server/resources'
  privileged: true # 特权模式
  networks: # 网络
    - hm-net

微服务中集成Seata

引入依赖


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

nacos上添加一个共享的seata配置,命名为shared-seata.yaml

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: hmall-nocos:8848 # nacos地址
      namespace: '' # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
      #集群配置
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: 'default' # 配置默认集群

XA模式

XA规范是X/Open组织定义的分布式事务处理标准,XA规范描述了全局的TM与局部RM 之间的接口,几乎所有主流的关系型数据库对XA规范提供了支持
SeataXA规范如下:

一阶段工作

  • RM注册分支事务到TC
  • RM执行分支事务但不提交SQL
  • RM报告执行状态到TC

二阶段工作

  • TC检测各分支事务的执行状态 如果都成功则通知所有RM提交事务 如果有一个失败则通知所有RM回滚事务
  • RM接收TC指令,提交或回滚

XA模式的优点

  • 事务的强一致性,满足ACID原则
  • 常用的数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才能释放,性能较差
  • 依赖关系型数据库实现事务

实现XA模式

Seatastarter已经完成了XA模式的自动装配。实现非常简单,步骤如下

修改application.yaml文件,开启XA模式

seata:
  data-source-proxy-mode: XA # 开启XA模式

给发起全局事务的入口方法添加@GlobalTransactional注解


@ApiOperation("创建订单")
@PostMapping
@GlobalTransactional // 开启XA模式全局事务
public Long createOrder(@RequestBody OrderFormDTO orderFormDTO) {
    return orderService.createOrder(orderFormDTO);
}

在参与事务的每一个方法中加入事务@Transactional注解,用于回滚事务


@Override
@Transactional //加入事务注解 用于回滚事务
public void removeByItemIds(Collection<Long> itemIds) {
//    -----
}

AT模式

Seata主推的是AT模式,AT同样是分阶段提交的事务模型,不过弥补了XA模型中资源(SQL)锁定周期过长的问题

一阶段RM的工作

  • 注册分支事务
  • 记录更新前的数据(数据快照)
  • 执行业务sql并立即提交
  • 报告事务状态

二阶段RM的工作

TC检测各分支事务的执行状态 如果都成功则通知所有RM删除快照 如果有一个失败则通知所有RM使用快照将数据恢复

实现AT模式

Seatastarter已经完成了AT模式的自动装配。实现非常简单,步骤如下

修改application.yaml文件,开启AT模式

seata:
  data-source-proxy-mode: AT # 开启AT模式

在每个微服务中添加快照表

因为每个微服务RM在发生错误时都需要快照来回滚,所以每一个微服务都需要添加一张快照表

-- for AT mode you must to init this sql for you business database. the seata server not need it.
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';

给发起全局事务的入口方法添加@GlobalTransactional注解


@ApiOperation("创建订单")
@PostMapping
@GlobalTransactional // 开启XA模式全局事务
public Long createOrder(@RequestBody OrderFormDTO orderFormDTO) {
    return orderService.createOrder(orderFormDTO);
}

注意

AT模式不需要在参与事务的每一个方法中加入事务@Transactional注解,因为AT模式不依赖数据库进行回滚

XA模式与AT模式的区别

  • XA模式一阶段不提交事务,锁定资源。AT模式一阶段直接提交,不锁定资源
  • XA模式依赖数据库机制实现回滚,AT模式利用数据快照实现数据回滚
  • XA模式强一致,AT模式最终一致
上次更新 2026/2/5 23:26:47