Apache Seata -- 一款开源的分布式事务解决方案

第一章 Seata 概述

一、分布式事务

1.1 分布式事务简介

(1)事务

事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。

(2)本地事务

​ 在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。

数据库事务的四大特性:ACID

  • A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况。
  • C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。比如:张三向李四转 100 元,转账前和转账后的数据是正确状态这叫一致性,如果出现张三转出 100 元,李四账户没有增加 100 元这就出现了数 据错误,就没有达到一致性。
  • I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务的运行过程的中间状态。通过配置事务隔离级别可以比避免脏读、重复读问题。
  • D(Durability):持久性,事务完成之后,该事务对数据的更改会持久到数据库,且不会被回滚。

数据库事务在实现时会将一次事务的所有操作全部纳入到一个不可分割的执行单元,该执行单元的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。

(3)分布式事务

随着互联网的快速发展,软件系统由原来的单体应用转变为分布式应用。

分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务。例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。

分布式事务产生的情景:

  • 跨JVM进程产生分布式事务
  • 跨数据库实例产生分布式事务
  • 多服务访问同一个数据库实例

1.2 分布式事务基础理论

(1)CAP定理

1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标:

  • consistency:一致性。用户访问分布式系统中的任意节点,得到的数据必须一致。
  • Availability:可用性。用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝。
  • Partition tolerance:分区容错性。
    • Partition:分区因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。
    • Tolerance:容错,在集群出现分区时,整个系统也要持续对外提供服务。

当分布式系统节点通过网络连接,就一定会出现分区问题(P),出现分区时系统的一致性(C)和可用性(A)就无法同时满足,此时就需要考虑是满足 CP 还是 AP。

(2)Base 理论

BASE 理论是对 CAP 的一种解决思路,包含三个思想:

  • Basically Available:基本可用。分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
  • Soft State:软状态。在一定时间内,允许出现中间状态,比如临时的不一致状态。
  • Eventually Consistent:最终一致性。虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。

分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴 CAP 定理和 BASE 理论:

  • AP 模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
  • CP 模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。

(3)分布式事务解决

解决分布式事务的思想和模型:

  • 全局事务:整个分布式事务
  • 分支事务:分布式事务中包含的每个子系统的事务
  • 最终一致思想:各分支事务分别执行并提交,如果有不一致的情况,再想办法恢复数据
  • 强一致思想:各分支事务执行完业务不要提交,等待彼此结果,而后统一提交或回滚

1.3 两阶段 2PC

2PC(Two-phase commit,三阶段提交) 即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2 是指两个阶段,P 是指准备阶段,C 是指提交阶段。

一阶段:

  1. 协调者向所有参与者发送事务请求,询问是否可执行事务操作,然后等待各个参与者的响应。
  2. 各个参与者接收到协调者事务请求后,执行事务操作(例如更新一个关系型数据库表中的记录),并将 Undo 和 Redo 信息记录事务日志中。
  3. 如果参与者成功执行了事务并写入 Undo 和 Redo 信息,则向协调者返回 YES 响应,否则返回 NO 响应。当然参与者也可能宕机,从而不会返回响应。

二阶段:

  1. 协调者向所有参与者发送 Commit/Rollback 请求。
  2. 事务提交参与者收到 CommitRollback 请求后,执行事务提交/回滚,提交/回滚完成后释放事务执行期占用的所有资源。反馈结果参与者执行事务提交/回滚后向协调者发送 Ack 响应。
  3. 完成事务接收到所有参与者的 Ack 响应后,完成事务提交/回滚。

2PC 的问题:

  • 同步阻塞:参与者在等待协调者的指令时,其实是在等待其他参与者的响应,在此过程中,参与者是无法进行其他操作的,也就是阻塞了其运行。倘若参与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去。
  • 单点:
    • 在2PC中,一切请求都来自协调者,所以协调者的地位是至关重要的,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源。
    • 如果协调者也是分布式,使用选主方式提供服务,那么在一个协调者挂掉后,可以选取另一个协调者继续后续的服务,可以解决单点问题。但是,新协调者无法知道上一个事务的全部状态信息(例如已等待Prepare响应的时长等),所以也无法顺利处理上一个事务。
  • 数据不一致:Commit 事务过程中 Commit/Rollack 请求可能因为协调者宕机或协调者与参与者网络问题丢失,那么就导致了部分参与者没有收到 Commt/Rollack 请求,而其他参与者则正常收到执行了 Commit/Rollack 操作,没有收到请求的参与者则继续阻塞。这时,参与者之间的数据就不再一致了。
  • 环境可靠性依赖:协调者 Prepare 请求发出后,等待响应,然而如果有参与者宕机或与协调者之间的网络中断,都会导致协调者无法收到所有参与者的响应,那么在 2PC 中,协调者会等待一定时间,然后超时后会触发事务中断,在这个过程中,协调者和所有其他参与者都是出于阻塞的

1.4 三阶段 3PC

3PC(Three-phase commit,三阶段提交)也叫三阶段提交协议,是在计算机网络及数据库的范畴下,使得一个分布式系统内的所有节点能够执行事务的提交的一种分布式算法。

3PC 主要是为了解决两阶段提交协议的阻塞问题,2PC 存在的问题是当协作者崩溃时,参与者不能做出最后的选择。因此参与者可能在协作者恢复之前保持阻塞。

除了引入超时机制之外,3PC 把 2PC 的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit 三个阶段。

CanCommit 阶段:

  1. 事务询问:协调者向参与者发送 CanCommit 请求,询问是否可以执行事务提交操作,然后开始等待参与者的响应。
  2. 响应反馈:参与者接到 CanCommit 请求之后,如果其自身认为可以顺利执行事务,则返回 Yes 响应,并进入预备状态。否则反馈 No。

PreCommit 阶段:

  • 在 CanCommit 阶段中,如果所有的参与者都返回 Yes 的话,那么就会进入 PreCommit 阶段进行事务预提交。这里的 PreCommit 阶段跟上面的第一阶段是差不多的,只不过这里协调者和参与者都引入了超时机制(2PC中只有协调者可以超时,参与者没有超时机制)。

DoCommit 阶段:

  1. 协调者向所有参与者发送 Commit/Rollback 请求。
  2. 事务提交参与者收到 CommitRollback 请求后,执行事务提交/回滚,提交/回滚完成后释放事务执行期占用的所有资源。反馈结果参与者执行事务提交/回滚后向协调者发送 Ack 响应。
  3. 完成事务接收到所有参与者的 Ack 响应后,完成事务提交/回滚。

3PC 对于协调者和参与者都设置了超时时间,避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地 commit 从而进行释放资源,这种机制也侧面降低了整个事务的阻塞时间和范围。

二、Seata 概述

2.1 Seata 简介

(1)Seata 简介

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

(2)Seata 发展历史

  1. 早在 2007 年,阿里巴巴和蚂蚁集团内部开发了分布式事务中间件,用于解决电商、支付、物流等业务场景中应用数据的一致性问题。内部项目分别被称为 TXC (Taobao Transaction Constructor)/XTS(eXtended Transaction Service),该项目几乎在每笔订单的交易支付链路几乎都有使用。

  2. 自 2013 年以来,阿里巴巴和蚂蚁集团已在阿里云和金融云上向企业客户分别发布了分布式事务云服务产品 GTS(global transaction service)/DTX(Distributed Transaction-eXtended),在各个行业领域积累了大量用户。

  3. 2019 年 1 月,阿里巴巴集团正式开源了该项目,项目命名为 Fescar (Fast & Easy Commit and Rollback))。项目开源以来,它受到了众多开发人员的热烈欢迎和赞扬,开源一周收获了超 3k star,曾一度蝉联 GitHub Trending 排行榜第一。

  4. 2019 年 4 月,蚂蚁集团数据中间件团队加入了 Fescar 社区。为了创建一个更加开放和中立的社区,Fescar 改名为 Seata(Simple Extensible Autonomous Transaction Architecture),代码仓库从 Alibaba organization 迁移到其独立的 Seata organization。

  5. 2019 年 12 月,Seata 开源项目正式发布 1.0.0 GA 版本,标志着项目已基本可生产使用。

  6. 2023 年 10 月,为了更好的通过社区驱动技术的演进,阿里和蚂蚁集团正式将 Seata 捐赠给 Apache 基金会,该提案已通过了 Apache 基金会的投票决议,Seata 正式进入 Apache 孵化器。

(3)Seata 角色

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

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

(4)Seata 方案

Seata提供了四种不同的分布式事务解决方案:

  • XA 模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC 模式:最终一致的分阶段事务模式,有业务侵入
  • AT 模式:最终一致的分阶段事务模式,无业务侵入,也是 Seata 的默认模式
  • SAGA 模式:长事务模式,有业务侵入

2.2 Seata 安装

(1)Seata 下载

进入 Seata 官网,如下:

点击下载,进入下载界面:

选择对应的版本和平台,下载即可。下载完成后,解压到指定目录,解压后的目录如下:

bin:

  • seata-server.bat:Windows 平台下启动文件
  • seata-server.sh:Linux 平台下启动文件

conf:

  • file.conf:文件存储的相关配置
  • registry.conf:注册中心和配置中心的相关配置
  • logback.xml:日志的相关配置

Seata 高版本的 seata-server 文件目录有所变化:

conf 目录下的 file.conf 和 registry.conf 文件被 application.yml 文件所代替。

2.3 Seata 部署

修改数据存储文件 file.conf,file.conf 文件如下:

sql 复制代码
## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "file"
  ## rsa decryption public key
  publicKey = ""
  ## file store property
  file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
  }

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
    url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"
    user = "mysql"
    password = "mysql"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
    ## redis mode: single、sentinel
    mode = "single"
    ## single mode property
    single {
      host = "127.0.0.1"
      port = "6379"
    }
    ## sentinel mode property
    sentinel {
      masterName = ""
      ## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381"
      sentinelHosts = ""
    }
    password = ""
    database = "0"
    minConn = 1
    maxConn = 10
    maxTotal = 100
    queryLimit = 100
  }
}

Seata 中文件存储有三种方式:

  • file:单机模式。全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)
  • db:高可用模式,全局事务会话信息通过db共享,相应性能差些
  • redis:Seata-Server 1.3 及以上版本支持,性能较高,存在事务信息丢失风险

修改数据存储方式,如修改为 db:

sql 复制代码
## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "db"
  ## rsa decryption public key
  publicKey = ""

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
    url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"
    user = "mysql"
    password = "mysql"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}

数据存储方式如果采用 db 方式的话,则需要创建对应的数据库表用于记录,创建数据库表的 SQL,在 Seata 项目源码的 /script/server/db 目录下可以获取:

全局事务表:

sql 复制代码
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;

分支事务表:

sql 复制代码
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;

锁记录表

sql 复制代码
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;

分布式锁记录表:

sql 复制代码
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 的注册中心和配置中心,对应文件为 registry.conf,文件内容如下:

bash 复制代码
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "file"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
    aclToken = ""
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "file"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
    dataId = "seataServer.properties"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
    aclToken = ""
  }
  apollo {
    appId = "seata-server"
    ## apolloConfigService will cover apolloMeta
    apolloMeta = "http://192.168.1.204:8801"
    apolloConfigService = "http://192.168.1.204:8080"
    namespace = "application"
    apolloAccesskeySecret = ""
    cluster = "seata"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
    nodePath = "/seata/seata.properties"
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

Seata 支持多种注册中心和配置中心,例如使用 nacos,配置如下:

bash 复制代码
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}

其中 nacos 的相关配置需要修改为实际使用的配置,Seata 提供了部分配置中心的默认配置 config.txt,在 Seata 项目源码的 script/config-center/ 路径下可以找到:

启动 Seata,使用如下命令启动:

sql 复制代码
# 直接启动方式
bin/seata-server.sh

# 添加配置方式启动
bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test
  • -h:host,指定在注册中心注册的 IP 地址
  • -p:port,指定启动端口,默认8091
  • -m:storeMode,指定事务日志存储方式,支持 file、db、redis
  • -n:serverNode,指定 seata-server 的节点 ID,如1,2,3
  • -e:seataEnv,指定 seata-server 运行环境,如 dev、test、prod,启动时会使用对应的配置文件如 registry-test.conf

Windows 平台下启动 seata-server.bat,Linux 平台下启动 seata-server.sh

2.4 Seata 集成

微服务集成 Seata,详细步骤如下:

引入依赖:

XML 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2.2.5.RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>

在 application.yml 文件中配置 Seata 的相关配置:

XML 复制代码
seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: "SEATA_GROUP"
      application: seata-tc-server
      username: nacos
      password: nacos
  # 事务组名称
  tx-service-group: seata-demo
  service:
    # 事务组与cluster的映射关系
    vgroup-mapping:
      seata-demo: SH

客户端数据库创建 undo_log 表:

sql 复制代码
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 XA 模式

1.1 XA 模式简介

(1)XA 模式

XA 模式是从 1.2 版本支持的事务模式。XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。

Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。

(2)优缺点

优点:

  • 与 Seata 支持的其它事务模式不同,XA 协议要求事务资源本身提供对规范和协议的支持,所以事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。
  • 业务无侵入:和 AT 一样,XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担。
  • 数据库的支持广泛:XA 协议被主流关系型数据库广泛支持,不需要额外的适配即可使用。
  • 强一致性,满足 ACID 原则。

缺点:

  • XA prepare 后,分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞等待。事务资源长时间得不到释放,锁定周期长,而且在应用层上面无法干预,性能差。
  • 依赖关系型数据库实现事务,如果数据库不支持事务则无法满足 XA 模式。

1.2 XA 模式原理

---阶段:

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

二阶段:

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

1.3 XA 模式实现

配置 Seata 事务模式为 XA 模式:

XML 复制代码
seata:
  data-source-proxy-mode: XA

使用注解 @GlobalTransantion

二、Seata AT 模式

2.1 AT 模式简介

(1)AT 模式

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。

AT 模式是 Seata 的默认模式。

在 AT 模式下,用户只需关注自己的业务 SQL,用户的业务 SQL 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

(2)优缺点

优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能比较好利用全局锁实现读写隔离
  • 没有代码侵入,框架自动完成回滚和提交

缺点:

  • 两阶段之间属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但比 XA 模式要好很多

2.2 AT 模式原理

(1)AT 模式原理分析

在一阶段,Seata 会拦截业务 SQL 并解析 SQL 语义,找到业务 SQL 要更新的业务数据,在业务数据被更新前,将其保存成 before image,然后执行业务 SQL 更新业务数据,在业务数据更新之后,再将其保存成 after image,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

二阶段如果是提交的话,因为业务 SQL 在一阶段已经提交至数据库,所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的业务 SQL,还原业务数据。回滚方式便是用 before image 还原业务数据。

注意在还原前要首先要校验脏写,对比数据库当前业务数据和 after image,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

3.3 AT 模式实现

配置 Seata 事务模式为 AT 模式:

XML 复制代码
seata:
  data-source-proxy-mode: AT

使用注解 @GlobalTransantion

(2)写隔离

AT 模式写隔离:

  • 一阶段本地事务提交前,需要确保先拿到全局锁 。
  • 拿不到全局锁 ,不能提交本地事务。
  • 拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

以一个示例来说明:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待全局锁 。

tx1 二阶段全局提交,释放全局锁 ,tx2 拿到全局锁提交本地事务。

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的全局锁等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程全局锁在 tx1 结束前一直是被 tx1 持有的,所以不会发生脏写的问题。

(3)读隔离

在数据库本地事务隔离级别"读已提交(Read Committed)"或以上的基础上,Seata(AT 模式)的默认全局隔离级别是"读未提交(Read Uncommitted)"。

如果应用在特定场景下,必需要求全局的"读已提交",目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

SELECT FOR UPDATE 语句的执行会申请全局锁 ,如果全局锁被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回。

三、Seata TCC 模式

3.1 TCC 模式简介

(1)TCC 模式

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。

(2)优缺点

优点:

  • TCC 完全不依赖底层数据库,能够实现跨数据库、跨应用资源管理,可以提供给业务方更细粒度的控制。
  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比 AT 模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

缺点:

  • TCC 是一种侵入式的分布式事务解决方案,需要业务系统自行实现 Try,Confirm,Cancel 三个操作,对业务系统有着非常大的入侵性,设计相对复杂
  • 软状态,事务是最终一致
  • 需要考虑 Confirm 和Cancel 的失败情况,做好幂等处理

3.2 TCC 模式原理

在两阶段提交协议中,资源管理器(RM, Resource Manager)需要提供"准备"、"提交"和"回滚" 3 个操作;而事务管理器(TM, Transaction Manager)分 2 阶段协调所有资源管理器,在第一阶段询问所有资源管理器"准备"是否成功,如果所有资源均"准备"成功则在第二阶段执行所有资源的"提交"操作,否则在第二阶段执行所有资源的"回滚"操作,保证所有资源的最终状态是一致的,要么全部提交要么全部回滚。

资源管理器有很多实现方式,其中 TCC(Try-Confirm-Cancel)是资源管理器的一种服务化的实现;TCC 是一种比较成熟的分布式事务解决方案,可用于解决跨数据库、跨服务业务操作的数据一致性问题;TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。

TCC 的 Try 操作作为一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 是二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。

3.3 TCC 模式实现

声明接口:

java 复制代码
@LocalTCC
public interface TccService {

    /**
     * 一阶段try逻辑
     */
    @TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
    void prepare(@BusinessActionContextParameter(paramName = "param") String param);

    /**
     * 二阶段提交方法
     */
    boolean commit(BusinessActionContext context);

    /**
     * 二阶段回滚方法
     */
    boolean rollback(BusinessActionContext context);
}

具体业务逻辑可以在该接口的实现类中完成。

TCC 模式问题:

  • 空回滚:当某分支事务的 try 阶段阻塞时,可能导致全局事务超时而触发二阶段的 cancel 操作。在未执行 try 操作时先执行了 cancel 操作,这时 cancel 不能做回滚,就是空回滚。
  • 业务悬挂:对于已经空回滚的业务,如果以后继续执行 try,就永远不可能 confirm 或 cancel,这就是业务悬挂。应当阻止执行空回滚后的 try 操作,避免悬挂。

四、Seata Saga 模式

4.1 Saga 模式简介

(1)Saga 模式

Saga 模式是 Seata 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

适用场景:

  • 业务流程长、业务流程多
  • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口

(2)优缺点

优势:

  • 一阶段提交本地事务,无锁,高性能
  • 事件驱动架构,参与者可异步执行,高吞吐
  • 补偿服务易于实现

缺点:

  • 不保证隔离性

4.2 Saga 模式原理

目前 Seata 提供的 Saga 模式是基于状态机引擎来实现的,机制是:

  • 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件
  • 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点
  • 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚
  • 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能

五、Seata 模式对比

| | XA | AT | TCC | Saga |
| 一致性 | 强一致 | 弱一致 | 弱一致 | 最终一致 |
| 隔离性 | 完全隔离 | 基于全局锁隔离 | 基于资源预留隔离 | 无隔离 |
| 代码入侵 | 无 | 无 | 需要编写三个接口 | 需要编写状态机和补偿业务 |
| 性能 | 差 | 好 | 非常好 | 非常好 |

场景 对一致性、隔离性有较高要求的业务 基于关系型数据库的大多数分布式事务 对性能要求较高的事务 有非关系型数据库参与的事务 业务流程长、业务流程多 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
相关推荐
小小工匠4 小时前
分布式协同 - 分布式事务_TCC解决方案
分布式事务·tcc
Java程序之猿4 小时前
微服务分布式(一、项目初始化)
分布式·微服务·架构
Yvemil76 小时前
《开启微服务之旅:Spring Boot Web开发举例》(一)
前端·spring boot·微服务
Yvemil710 小时前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
维李设论10 小时前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
jwolf213 小时前
基于K8S的微服务:一、服务发现,负载均衡测试(附calico网络问题解决)
微服务·kubernetes·服务发现
小小工匠14 小时前
分布式协同 - 分布式事务_2PC & 3PC解决方案
分布式·分布式事务·2pc·3pc
Yvemil714 小时前
《开启微服务之旅:Spring Boot Web开发举例》(二)
前端·spring boot·微服务
一个儒雅随和的男子15 小时前
微服务详细教程之nacos和sentinel实战
微服务·架构·sentinel
Yvemil717 小时前
《开启微服务之旅:Spring Boot Web开发》(三)
前端·spring boot·微服务