SpringCloud Alibaba Seata基础

1. 分布式事务简介

1.1 概念

分布式事务是指涉及多个独立事务资源的操作(如数据库),这些资源可以分布在不同的物理或逻辑位置上,通常出现在分布式系统中。在分布式事务中,确保所有资源操作的一致性和可靠性变得复杂,因此需要一种有效的事务管理机制来保障分布式系统对于资源操控的可靠性。

2种分布式事务协议:

  1. 两阶段提交(2PC)协议:在2PC模型中,有一个协调者(通常是事务管理器)和多个参与者(分布式事务资源)。它分为准备和提交两个阶段,协调者发出准备请求(prepare),参与者返回准备就绪或失败,然后协调者发出提交(commit)或回滚(rollback)请求。这种模型确保资源的最终一致性,但存在阻塞问题和单点故障。

  2. 三阶段提交(3PC)协议:3PC协议相较于2PC协议增加一个一个准备阶段,主要包括CanCommit(准备阶段)、PreCommit(提交准备阶段)和DoCommit(提交阶段)。CanCommit阶段,协调器询问所有参与者是否可以提交事务,而参与者回复"同意"或"中止"。在PreCommit阶段,如果所有参与者同意,协调器通知它们准备提交。最后,在DoCommit阶段,协调器要求所有参与者最终提交事务。

4种分布式事务模型:

  1. AT(Auto Transaction)模式:AT模式是一种无侵入的两阶段提交的分布式事务模型。一阶段:Seata会拦截业务SQL生成"before image"在提交事务,而后生成"after image";二阶段,提交成功则结束事务,如果回滚的话则根据"before image"和"after image"对每一个数据库进行回滚。

  2. TCC(Try-Confirm-Cancel):与AT模式类似,TCC模式也是一个两阶段提交的分布式事务模型。不同在于,TCC需要根据自己的业务场景实现Try、Confirm和Cancel三个操作;事务发起方在一阶段执行Try操作,在二阶段提交执行Confirm方法,二阶段回滚执行Cancel方法。

  3. SAGA模式:长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统。Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。

  4. XA模式:采用了分布式事务的XA协议,它将所有参与者的事务协调起来,要求所有资源都支持XA事务。XA模式通常由Seata代理来实现,是一种强一致性的分布式事务模型,但是其效率较低。。

1.2 ACID

​ ACID 是数据库管理系统中一组重要的特性和属性,用于确保数据库事务的可靠性和一致性。ACID 是一个缩写,表示以下四个核心属性:

  1. 原子性 (Atomicity): 原子性确保一个事务(一系列数据库操作)要么完全成功,要么完全失败,没有中间状态。如果一个事务中的任何一个操作失败,整个事务将被回滚,以确保数据库保持一致性。原子性的基本思想是 "all or nothing"。

  2. 一致性 (Consistency): 一致性确保事务在执行前后,数据库从一个一致的状态转移到另一个一致的状态。这意味着事务必须遵守数据库的约束和规则,不会破坏数据库的完整性。如果一个事务违反了一致性规则,它将被回滚,以确保数据的一致性。

  3. 隔离性 (Isolation): 隔离性确保一个事务的执行不会受到其他并发事务的影响。每个事务都应该似乎在一个独立的环境中执行,而不会受到其他事务的干扰。这可以通过使用锁和事务隔离级别来实现,以防止数据竞争和不一致的结果。

  4. 持久性 (Durability): 持久性确保一旦事务成功提交,其结果将永久保存在数据库中,即使在系统故障或崩溃后也是如此。这通常涉及将事务日志写入稳定的存储介质,以便在需要时可以恢复数据。

1.3 事务

​本地事务和分布式事务是涉及事务管理的两个重要概念,它们有不同的范围和复杂性:

  1. 本地事务 (Local Transaction):

    • 本地事务通常指的是在单一数据源(例如,单个数据库)上执行的事务操作。这种事务涉及对单个数据存储的读取和写入操作

    • 本地事务的范围受限于单个数据库,因此,如果在事务中的任何操作失败,可以轻松地回滚整个事务,确保数据的一致性。

    • 本地事务通常由数据库管理系统(DBMS)自己管理,不涉及多个不同的数据存储。

  2. 分布式事务 (Distributed Transaction):

    • 分布式事务是在多个不同的数据源(通常是多个数据库或服务)之间执行的事务操作。这些数据源可以分布在不同的物理位置或服务器上。

    • 分布式事务的复杂性更高,因为它需要确保在不同数据源之间的操作一致性。如果在事务中的某个数据源上的操作失败,必须确保其他数据源上的操作也回滚,以保持整个事务的一致性。

    • 分布式事务通常需要使用分布式事务管理器(如 XA 协议)来协调各个数据源之间的操作。这个协调器确保所有参与者在事务成功或回滚时协同工作。

分布式事务通常出现在复杂的分布式系统中,例如大型企业应用程序或微服务架构中,其中不同的服务可能存储在不同的数据库中。保持数据一致性对于这些应用程序至关重要,因此需要使用分布式事务来管理多个服务之间的数据交互。

举例:

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

从上面的图中我们可以看出,每个微服务都拥有自己的数据源,只要涉及到操作多个数据源就可能会产生事务问题,当然在实际开发中我们要尽量避免这种问题的出现,如果避免不了就需要进行解决,在微服务系统架构中,目前比较好用的方案就是Seata。

1.4 分布式事务理论

目前各种项目都逐渐向分布式服务做转换,微服务已经广泛存在,本地事务已经无法满足分布式的要求,由此分布式事务的问题诞生。分布式事务被称为世界性难题,目前分布式事务存在两大理论依据:CAP定律和Base定律

1.4.1 CAP定律

​CAP 定律是分布式计算领域的一个基本概念,它描述了在分布式系统中三个关键属性之间的权衡。CAP 表示 Consistency(一致性)、Availability(可用性)、和 Partition tolerance(分区容忍性)。这个定律由计算机科学家 Eric Brewer 在 2000 年提出,强调在分布式系统中很难同时满足这三个属性,必须进行权衡。

  • 一致性 (Consistency): 这意味着在分布式系统中的所有节点都具有相同的数据视图。如果一个节点对数据进行了更改,那么所有其他节点应该能够立即看到这个更改。一致性要求在系统执行写操作后,任何后续读操作都应该返回更新后的值。

  • 可用性 (Availability): 这意味着系统中的每个请求都应该得到响应,即使其中的某些节点出现故障或无法提供服务。可用性要求系统对请求作出响应,无论请求成功还是失败,而不应该出现无响应的情况。

  • 分区容忍性 (Partition tolerance): 这意味着系统在面临网络分区或节点之间通信失败的情况下,仍然能够继续运行。分区容忍性是指系统能够处理由于网络故障或节点故障导致的部分失去联系的情况,而不会影响整个系统的可用性。

CAP 定律表明在分布式系统中,最多只能满足 CAP 中的两个属性,而无法同时满足三个。这意味着在设计分布式系统时,必须进行权衡和选择,以满足应用程序的需求。以下是一些典型的权衡:

  • CA: 强调一致性和可用性,但不支持分区容忍性。这种模型适用于传统的关系数据库系统,其中一致性和可用性非常重要。

  • CP: 强调一致性和分区容忍性,但可能牺牲可用性。这种模型适用于需要分区容忍性的应用程序,如分布式数据库系统。

  • AP: 强调可用性和分区容忍性,但可能牺牲一致性。这种模型适用于需要高可用性和分区容忍性的应用程序,如大规模分布式存储系统。

CAP 定律的概念帮助了分布式系统设计者和架构师理解在不同情况下需要做出的权衡选择,以满足应用程序的特定需求。

1.4.2 BASE定律

​BASE 定律是与 CAP 定律(一致性、可用性、分区容忍性)相关的另一个分布式系统原则,它强调在某些情况下,分布式系统可能无法实现强一致性,而采用柔性一致性的方式来处理数据。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采取适当的方式来使系统达到最终一致性。

BASE 是以下三个属性的缩写:

  • 基本可用性(Basically Available): 分布式系统在面临故障或分区的情况下仍然能够提供基本的可用性。这意味着系统可以部分响应请求,即使不能提供完整的功能。

  • 软状态(Soft state): 系统的状态可以在时间上演化,不要求实时的一致性。这允许数据在系统中的不同部分之间存在瞬时的不一致性。即允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

  • 最终一致性(Eventually Consistent): 最终一致性是 BASE 定律的核心概念。它指的是虽然系统中的数据副本可能不会立即达到一致状态,但最终在一段时间内,数据会趋向于一致。这种一致性模型允许系统在某些情况下牺牲强一致性,以获得更高的可用性和分区容忍性。

BASE 定律的概念适用于那些要求高可用性、分区容忍性以及允许一时的数据不一致性的分布式系统。与传统的 ACID 模型(强一致性、原子性、隔离性、持久性)不同,BASE 模型更加灵活,适用于互联网规模的分布式应用,如社交媒体、在线协作工具和大规模数据存储系统。需要注意的是,BASE 模型通常需要开发者自行设计和实现,而不是由数据库系统自动提供。开发者需要在应用程序层面处理数据的最终一致性,确保数据在系统中逐渐趋向于一致状态。这意味着在 BASE 模型中,开发者需要更多的工作来处理数据同步和冲突解决。

2. Seata简介

2.1 什么是Seata?

官网:seata.io/zh-cn/docs/...

​Seata(原名Fescar)是一个开源的分布式事务解决方案,用于处理分布式系统中的分布式事务问题。它提供了强一致性、高可用性、高性能的事务支持,通常用于微服务架构中,以确保跨多个微服务的事务操作是原子的。​为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata主要包括以下三个核心模块:

  • TC(Transaction Coordinator,事务协调器):负责事务的协调和管理,确保分布式事务的一致性,Seata部署在服务端的程序就可以理解为一个TC,用于全局跟踪分布式事务;

  • TM(Transaction Manager,事务管理器):用于管理全局事务的生命周期,包括事务的开始、提交和回滚,可以理解为Client段,用于开启一个分布式事务;

  • RM(Resource Manager,资源管理器):与各种资源(如数据库、消息队列)集成,协助进行本地事务的提交和回滚,可以理解为数据库,消息队列等资源;

Seata的基本工作原理如下:

  • 当一个全局事务开始时,TM会生成一个全局事务ID,并将此ID传播到各个微服务。

  • 各个微服务将本地事务注册到TC上,由TC来协调和全局事务的提交或回滚。

  • 如果某个本地事务失败,TC将通知其他微服务回滚,确保全局事务的一致性。

Seata的优势在于它提供了一种可插拔的方式,可以轻松与不同的数据存储和消息队列集成,同时具备较好的性能和可伸缩性,是解决分布式事务问题的重要工具。

2.2 Seata的分布式事务整体机制

2.1.1 2PC

​Seata的所有事务模式都是基于两阶段提交协议的​(Two-Phase Commit,2PC),2PC是一种分布式系统中用于确保事务原子性和一致性的协议。它包含两个阶段,通常用于多个参与者(如数据库、应用程序等)之间的协作,以确保在分布式环境中的事务要么全部提交,要么全部回滚。以下是2PC的工作原理:

  1. 准备阶段(Phase 1 - Prepare Phase):

    • 事务协调者(Coordinator)向所有参与者发送事务准备请求(Prepare)。
    • 各参与者执行事务操作,但不提交事务,而是将操作前状态和操作结果分别记录在一个日志(undo log/redo log)中,并等待协调者的指示。
    • 如果所有参与者成功执行事务操作,它们向协调者发送准备完成的消息。
  2. 提交阶段(Phase 2 - Commit Phase):

    • 事务协调者在准备阶段收到所有参与者的准备完成消息后,向所有参与者发送提交事务(commit)的请求。
    • 参与者收到提交请求后,如果之前的准备阶段执行成功,它们会正式提交事务并释放资源。如果有任何一方准备或提交失败,所有参与者将回滚事务(rollback),确保一致性。

2PC的优点是能够确保分布式事务的一致性,但它也有一些缺点,如阻塞问题(如果有一个参与者长时间未响应,整个过程将被阻塞)和单点故障(如果事务协调者故障,整个过程无法完成)。因此,2PC在某些情况下可能不是最佳选择,而一些分布式数据库系统采用了更先进的协议来解决这些问题。

2.1.2 2PC的缺点

  • 同步阻塞:RM在等待TC的指令过程中,RM是处于一个阻塞状态的,也就是无法进行其它的操作。如果RM和TC之间的网络通讯出现问题导致收不到TC的信息,那么就会导致TC一直阻塞下去;

  • 单点:在2PC中所有请求都是来来自于TC,如果TC宕机的话就会导致RM处于阻塞状态。但是,如果TC采用分布式的方式进行部署是可以解决单点问题。但是由选主方式推选出来的新TC无法知道上一个事务的全部信息,也无法顺利处理下一个事务。

  • 数据不一致:事务commit过程中,commit请求和rollback请求可能因为协调者宕机或者网络问题,导致部分参与者没有收到commit/rollback请求,而另一些参与者却收到了,这种情况下就会导致参与者之间的数据不一致。

2.1.3 举例:

  • 比如相亲2个人要去吃饭,饭店老板要求先垫付再吃饭,这时男女双方提出AA,也就是说只有男女双方都付钱才能落座吃饭,但是只要2个人中有一个不统一付款就不能落座吃饭。

  • 准备阶段:老板要求男方付款,男方付款。老板要求女方付款,女方付款;

  • 提交阶段:老板出餐,两人纷纷落座,如果男女双方任何一人拒绝付款,老板拒绝出餐,已付的钱原路返回;

整个事务过程由事务管理器和参与者组成,老板就是事务管理器,男女双方就是参与者,师傅管理器决策整个分布式事务在计算机中关系数据库支持的两个阶段提交协议。

3. Seata安装

3.1 Seata安装

Seata的Server端存储模式支持三种:

  1. file:单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)

  2. DB:高可用模式,全局事务会话信息通过DB共享,性能相对较差。

  3. redis:性能较高,存在事务信息丢失风险,需要配合实际场景使用。

3.2 Docker部署单机版Seata

  • 启动seata-server实例

$ docker run --name seata-server -p 8091:8091 -p 7091:7091 seataio/seata-server:1.5.0

  • 指定seata-server IP和端口启动

$ docker run --name seata-server

-p 8091:8091

-p 7091:7091

-e SEATA_IP=127.0.0.1

-e SEATA_PORT=8091

seataio/seata-server

  • Docker compose 启动,docker-compose.yaml 示例

version: "3" services: seata-server: image: seataio/seata-server:${latest-release-version} hostname: seata-server ports: - "8091:8091" - "7091:7091" environment: - SEATA_PORT=8091 - STORE_MODE=file

  • 容器命令行及查看日志

<math xmlns="http://www.w3.org/1998/Math/MathML"> d o c k e r e x e c − i t s e a t a − s e r v e r s h docker exec -it seata-server sh </math>dockerexec−itseata−serversh docker logs -f seata-server

  • 使用自定义配置文件

自定义配置文件需要通过挂载文件的方式实现,将宿主机上的 application.yml 挂载到容器中相应的目录 首先启动一个用户将resources目录文件拷出的临时容器

bash 复制代码
docker run -d -p 8091:8091 -p 7091:7091  --name seata-serve seataio/seata-server:latest
docker cp seata-serve:/seata-server/resources /User/seata/config

拷出后可以,可以选择修改application.yml再cp进容器,或者rm临时容器,如下重新创建,并做好映射路径设置

  • 指定 application.yml
shell 复制代码
$ docker run --name seata-server \
        -p 8091:8091 \
        -p 7091:7091 \
        -v /User/seata/config:/seata-server/resources  \
        seataio/seata-server

其中 -e 用于配置环境变量, -v 用于挂载宿主机的目录,如果是以file存储模式运行,请加上-v /User/seata/sessionStore :/seata-server/sessionStore 将file的数据文件映射到宿主机,以防数据丢失(注:/User/seata/config和/User/seata/sessionStore可自定义宿主机目录,无需照搬)

接下来你可以看到宿主机对应目录下已经有了,logback-spring.xml,application.example.yml,application.yml 如果比较熟悉springboot,那么接下来就很简单了,只需要修改application.yml即可,详细配置可以参考application.example.yml,该文件存放了所有可使用的详细配置

3.3 Docker Compose部署Seata

seata.io/zh-cn/docs/...

3.3.1 无注册中心,file存储

该模式下既不需要注册中心,也不需要三方存储中心,其实就是个单机版的seata,其docker-compose.yml文件如下:

yaml 复制代码
version: "3.1"
services:
  seata-server:
    image: seataio/seata-server
    hostname: seata-server
    ports:
      - "7091:7091"
      - "8091:8091"
    environment:
      - SEATA_PORT=8091
      - STORE_MODE=file

3.3.2 无注册中心,db存储

DB模式需要先在数据库中创建对应表,脚本:​github.com/seata/seata...

  • 配置application.yml文件

    这里默认所有的服务都是部署在本地的,即127.0.0.1,具体的生产环境根据生产环境的需要进行部署。注意这里的application.yml文件是seata容器部署成功之后在seata-server/resources/application.yml中的文件,实际上可以看出Seata本质上也是一个Spring Boot项目。

yaml 复制代码
server:
port: 7091

spring:
  application:
    name: seata-server

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

console:
  user:
    username: seata
    password: seata

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: file
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: file
  store:
    # support: file 、 db 、 redis
    mode: db
    db:
      datasource: druid
      dbType: mysql
      # 需要根据mysql的版本调整driverClassName
      # mysql8及以上版本对应的driver:com.mysql.cj.jdbc.Driver
      # mysql8以下版本的driver:com.mysql.jdbc.Driver
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/seata-server?useUnicode=true&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false
      user: root
      password: root
      
  #  server:
  #    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
* 配置docker-compose.yml文件
  version: "3.1"
services:
  seata-server:
    image: seataio/seata-server:1.5.2
    ports:
      - "7091:7091"
      - "8091:8091"
    environment:
      - STORE_MODE=db
      # 以SEATA_IP作为host注册seata server
      - SEATA_IP=seata_ip
      - SEATA_PORT=8091
    volumes:
      - "/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime"        #设置系统时区
      - "/usr/share/zoneinfo/Asia/Shanghai:/etc/timezone"  #设置时区
      # 假设我们通过docker cp命令把资源文件拷贝到相对路径`./seata-server/resources`中
      # 如有问题,请阅读上面的[注意事项]以及[使用自定义配置文件]
      - "./seata-server/resources:/seata-server/resources"

3.3.3 nacos注册中心,db部署

Seata支持注册服务到nacos中,以及支持Seata所有配置都放到Nacos的配置中心,在Nacos中统一维护,高可用模式就需要配合Nacos来完成:

同样需要先行根据数据库脚本创建表结构,​github.com/seata/seata...

  • 配置application.yml
yaml 复制代码
server:
port: 7091

spring:
  application:
    name: seata-server

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

console:
  user:
    username: seata
    password: seata

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: nacos_ip:nacos_port
      namespace: seata-server
      group: SEATA_GROUP
      username: nacos
      password: nacos
      data-id: seataServer.properties

  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: nacos_ip:nacos_port
      group: SEATA_GROUP
      namespace: seata-server
      # tc集群名称
      cluster: default
      username: nacos
      password: nacos
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
  • 配置nacos的配置中心

    需要在nacos新建配置,此处dataId为seataServer.properties

ini 复制代码
store.mode=db
#-----db-----
store.db.datasource=druid
store.db.dbType=mysql
# 需要根据mysql的版本调整driverClassName
# mysql8及以上版本对应的driver:com.mysql.cj.jdbc.Driver
# mysql8以下版本的driver:com.mysql.jdbc.Driver
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata-server?useUnicode=true&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false
store.db.user= root
store.db.password=root
# 数据库初始连接数
store.db.minConn=1
# 数据库最大连接数
store.db.maxConn=20
# 获取连接时最大等待时间 默认5000,单位毫秒
store.db.maxWait=5000
# 全局事务表名 默认global_table
store.db.globalTable=global_table
# 分支事务表名 默认branch_table
store.db.branchTable=branch_table
# 全局锁表名 默认lock_table
store.db.lockTable=lock_table
# 查询全局事务一次的最大条数 默认100
store.db.queryLimit=100


# undo保留天数 默认7天,log_status=1(附录3)和未正常清理的undo
server.undo.logSaveDays=7
# undo清理线程间隔时间 默认86400000,单位毫秒
server.undo.logDeletePeriod=86400000
# 二阶段提交重试超时时长 单位ms,s,m,h,d,对应毫秒,秒,分,小时,天,默认毫秒。默认值-1表示无限重试
# 公式: timeout>=now-globalTransactionBeginTime,true表示超时则不再重试
# 注: 达到超时时间后将不会做任何重试,有数据不一致风险,除非业务自行可校准数据,否者慎用
server.maxCommitRetryTimeout=-1
# 二阶段回滚重试超时时长
server.maxRollbackRetryTimeout=-1
# 二阶段提交未完成状态全局事务重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.committingRetryPeriod=1000
# 二阶段异步提交状态重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.asynCommittingRetryPeriod=1000
# 二阶段回滚状态重试回滚线程间隔时间  默认1000,单位毫秒
server.recovery.rollbackingRetryPeriod=1000
# 超时状态检测重试线程间隔时间 默认1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器
server.recovery.timeoutRetryPeriod=1000
* 配置docker-compose.yml文件
  version: "3.1"
services:
  seata-server:
    image: seataio/seata-server:1.5.2
    ports:
      - "7091:7091"
      - "8091:8091"
    environment:
      - STORE_MODE=db
      # 以SEATA_IP作为host注册seata server
      - SEATA_IP=seata_ip
      - SEATA_PORT=8091
    volumes:
      - "/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime"        #设置系统时区
      - "/usr/share/zoneinfo/Asia/Shanghai:/etc/timezone"  #设置时区
      # 假设我们通过docker cp命令把资源文件拷贝到相对路径`./seata-server/resources`中
      # 如有问题,请阅读上面的[注意事项]以及[使用自定义配置文件]
      - "./seata-server/resources:/seata-server/resources"

3.3.4 高可用部署

​ seata高可用依赖于注册中心、数据库,可不依赖配置中心。需要保证多个Seata Server使用同一个注册中心和同一个存储中心,这样才能形成高可用部署。​同样需要先行根据数据库脚本创建表结构,github.com/seata/seata...

  • 配置application.yml
yaml 复制代码
server:
port: 7091

spring:
  application:
    name: seata-server

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

console:
  user:
    username: seata
    password: seata

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: nacos_ip:nacos_port
      namespace: seata-server
      group: SEATA_GROUP
      usernam: nacos
      password: nacos
      data-id: seataServer.properties

  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: nacos_ip:nacos_port
      group: SEATA_GROUP
      namespace: seata-server
      # tc集群名称
      cluster: default
      username: nacos
      password: nacos
    #  store:
    # support: file 、 db 、 redis
  #    mode: file
  #  server:
  #    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
      
  • nacos配置中心新增配置

    需要在nacos新建配置,此处dataId为seataServer.properties

ini 复制代码
store.mode=db
#-----db-----
store.db.datasource=druid
store.db.dbType=mysql
# 需要根据mysql的版本调整driverClassName
# mysql8及以上版本对应的driver:com.mysql.cj.jdbc.Driver
# mysql8以下版本的driver:com.mysql.jdbc.Driver
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata-server?useUnicode=true&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false
store.db.user= 用户名
store.db.password=密码
# 数据库初始连接数
store.db.minConn=1
# 数据库最大连接数
store.db.maxConn=20
# 获取连接时最大等待时间 默认5000,单位毫秒
store.db.maxWait=5000
# 全局事务表名 默认global_table
store.db.globalTable=global_table
# 分支事务表名 默认branch_table
store.db.branchTable=branch_table
# 全局锁表名 默认lock_table
store.db.lockTable=lock_table
# 查询全局事务一次的最大条数 默认100
store.db.queryLimit=100


# undo保留天数 默认7天,log_status=1(附录3)和未正常清理的undo
server.undo.logSaveDays=7
# undo清理线程间隔时间 默认86400000,单位毫秒
server.undo.logDeletePeriod=86400000
# 二阶段提交重试超时时长 单位ms,s,m,h,d,对应毫秒,秒,分,小时,天,默认毫秒。默认值-1表示无限重试
# 公式: timeout>=now-globalTransactionBeginTime,true表示超时则不再重试
# 注: 达到超时时间后将不会做任何重试,有数据不一致风险,除非业务自行可校准数据,否者慎用
server.maxCommitRetryTimeout=-1
# 二阶段回滚重试超时时长
server.maxRollbackRetryTimeout=-1
# 二阶段提交未完成状态全局事务重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.committingRetryPeriod=1000
# 二阶段异步提交状态重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.asynCommittingRetryPeriod=1000
# 二阶段回滚状态重试回滚线程间隔时间  默认1000,单位毫秒
server.recovery.rollbackingRetryPeriod=1000
# 超时状态检测重试线程间隔时间 默认1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器
server.recovery.timeoutRetryPeriod=1000
* 准备docker-compose.yml文件
  ersion: "3.1"
services:
  seata-server-1:
    image: seataio/seata-server:${latest-release-version}
    ports:
      - "7091:7091"
      - "8091:8091"
    environment:
      - STORE_MODE=db
      # 以SEATA_IP作为host注册seata server
      - SEATA_IP=seata_ip
      - SEATA_PORT=8091
    volumes:
      - "/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime"        #设置系统时区
      - "/usr/share/zoneinfo/Asia/Shanghai:/etc/timezone"  #设置时区
      # 假设我们通过docker cp命令把资源文件拷贝到相对路径`./seata-server/resources`中
      # 如有问题,请阅读上面的[注意事项]以及[使用自定义配置文件]
      - "./seata-server/resources:/seata-server/resources"

  seata-server-2:
    image: seataio/seata-server:${latest-release-version}
    ports:
      - "7092:7091"
      - "8092:8092"
    environment:
      - STORE_MODE=db
      # 以SEATA_IP作为host注册seata server
      - SEATA_IP=seata_ip
      - SEATA_PORT=8092
    volumes:
      - "/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime"        #设置系统时区
      - "/usr/share/zoneinfo/Asia/Shanghai:/etc/timezone"  #设置时区
      # 假设我们通过docker cp命令把资源文件拷贝到相对路径`./seata-server/resources`中
      # 如有问题,请阅读上面的[注意事项]以及[使用自定义配置文件]
      - "./seata-server/resources:/seata-server/resources"

3.5 ​使用 Kubernetes 部署 Seata Server

3.5.1 ​快速启动

  • 创建 seata-server.yaml
yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: seata-server
  namespace: default
  labels:
    k8s-app: seata-server
spec:
  type: NodePort
  ports:
    - port: 8091
      nodePort: 30091
      protocol: TCP
      name: http
  selector:
    k8s-app: seata-server

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: seata-server
  namespace: default
  labels:
    k8s-app: seata-server
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: seata-server
  template:
    metadata:
      labels:
        k8s-app: seata-server
    spec:
      containers:
        - name: seata-server
          image: docker.io/seataio/seata-server:latest
          imagePullPolicy: IfNotPresent
          env:
            - name: SEATA_PORT
              value: "8091"
            - name: STORE_MODE
              value: file
          ports:
            - name: http
              containerPort: 8091
              protocol: TCP
* 执行命令
  kubectl apply -f seata-server.yaml

3.6 表结构和SQL语句

  • branch_table:分支事务会话表

  • global_table:全局事务会话表

  • lock_table:锁表数据

less 复制代码
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` (`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);

4. Seata-AT模式(最终一致性)

4.1 概述

Seata AT(Atomic Transaction,原子事务)模式是一种适用于分布式系统的事务模式,在AT模式下用户只需要关注自己的"业务SQL",用户的"业务SQL"作为一阶段,Seata会自动生成事务的第二阶段提交和回滚操作。它遵循 ACID(原子性、一致性、隔离性、持久性)属性,确保在多个微服务之间的事务原子性和最终一致性。

Seata AT 模式的基本组成如下:

  • 事务发起者:通常是业务微服务,它开始一个分布式事务并向 Seata 事务协调者注册该事务。

  • 事务协调者:Seata事务协调者负责协调和管理所有参与该事务的微服务。

  • 参与者:各个微服务参与该分布式事务。它们执行事务操作,并在事务协调者的协调下保持一致。

Seata AT 模式的关键特点包括:

  • 全局事务ID:每个全局事务都有一个唯一的事务ID。

  • 分支事务ID:全局事务可以包含一个或多个分支事务,每个分支事务都有一个唯一的分支事务ID。

  • 2PC:AT事务模式本质上也是一个2PC的事务模式,在第一阶段Seata会拦截本地SQL,生成"undo log"和"redo log",并加行锁;第二阶段,提交成功则参与者执行redo log,否则执行undo log进行回滚。

Seata AT 模式适用于需要最一致性的分布式事务,但它也需要进行适当的配置和部署以确保高可用性。 Seata 提供了一套完整的工具和库,以简化分布式事务的管理和协调,使开发人员更容易实现分布式系统中的原子性操作。

4.2 AT模式的优缺点

以下是Seata-AT模式的优缺点:

​优点:

  • 简化开发:AT模式相对于TCC(Try-Confirm-Cancel)等复杂的分布式事务模式来说,更容易开发和维护。它类似于本地事务,开发人员可以像编写本地事务一样来编写分布式事务,对于代码层面只需要一个@GlobalTranscation注解即可开启分布式事务。

  • 高性能:AT模式因为不需要像TCC那样执行额外的确认和取消操作,所以在性能上更高效。它减少了对数据库和资源的额外负担。

  • 适用性广泛:AT模式适用于大多数应用场景,尤其是传统的关系型数据库操作,可以轻松地与各种数据库集成。

  • 开箱即用:Seata提供了全面的分布式事务解决方案,AT模式是默认支持的,因此不需要额外的定制和配置。

缺点:

  • 有限的适用性:AT模式可能不适用于所有类型的分布式事务,特别是那些需要多个阶段确认和取消操作的复杂事务。

  • 数据库锁风险:在AT模式中,全局事务中的所有分支事务都会在最终提交前持有数据库锁,这可能导致数据库性能问题和潜在的死锁。

  • 不支持跨多个资源类型:AT模式不太适合需要跨越多种资源类型(如消息队列、文件系统等)的复杂事务,因为不同资源的事务管理可能各不相同。

  • 不能保障强一致性:由于AT模式会先提交SQL再进行全局事务的判断,因此对于参与者而言会存在中间状态,这就有可能导致出现脏读等情况的发生,无法保证数据的强一致性。

4.3 整体机制

Seata的AT模式本质上也是一个2PC(两阶段提交)协议:

一阶段

在阶段中,Seata会拦截"业务SQL",首先解析SQL语意,找到要更新的业务数据,在数据被更新前,保存下来"undo log",然后执行"业务SQL"更新数据,更新之后再次保存数据"redo log",最后生成行锁,这些操作都在本地数据库事务内完成,这样保证了一阶段的原子性。

二阶段

相对于一阶段,第二阶段比较简单,负责整体的回滚和提交。

  • 如果整体事务提交成功,只需要将第一阶段保存的undo log 、redo log 和 行锁删除即可。

  • 如果之前的一阶段中有本地事务没有通过,那么就执行全局回滚,否则执行全局提交,回滚用到的就是一阶段记录的"undo log",通过回滚记录生成反向更新SQL并执行,以完成分支回滚。但是在还原数据之前首先要进行脏写校验,如果"数据库数据"和"redo log"两份数据完全一致就说明没出现脏写,可以进行业务数据还原。如果数据不一致则说明出现了脏写,需要人工处理。当然事务完成后会释放所有资源和删除所有日志。

4.4 Seata-AT模式的应用案例

该案例可以参考我的gitee项目,这里创建基本项目的步骤直接省略:​gitee.com/lei-qinghua...

简单描述一下该案例,在该案例中我们创建了一个订单服务和一个库存服务,订单服务主要创建新的订单并且将新订单的数据写入数据库。库存服务主要是当新订单建立之后,库存表中对应的库存数量减少。这里涉及到分布式事务的概念,因为我们必须保证两张表同时更新成果或者失败会滚。

    1. 设计2个服务:一个订单服务cloud-alibaba-seata-order-8801,一个库存服务 cloud-alibaba-seata-stock-8802
    • cloud-alibaba-seata-order-8801:订单服务,模拟创建订单,并且通过openfeign访问 cloud-alibaba-seata-stock-8802的库存服务来进行库存的增减

    • cloud-alibaba-seata-stock-8802:库存服务,当订单生成之后,库存数量需要随着订单数据库同步减少。

    1. 创建相关数据库表
    • 每个数据库都需要创建undo_log表,这个表是用来进行数据回滚的
    r 复制代码
        CREATE TABLE `undo_log` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `branch_id` bigint(20) NOT NULL,
      `xid` varchar(100) NOT NULL,
      `context` varchar(128) NOT NULL,
      `rollback_info` longblob NOT NULL,
      `log_status` int(11) NOT NULL,
      `log_created` datetime NOT NULL,
      `log_modified` datetime NOT NULL,
      `ext` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    • 创建stock库存:
    sql 复制代码
        CREATE TABLE `dbtest`.`Untitled`  (
      `id` bigint NOT NULL,
      `product_id` bigint NOT NULL DEFAULT 0,
      `count` int NOT NULL DEFAULT 0,
      `money` decimal(10, 2) NOT NULL DEFAULT 0.00,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
    • 创建order订单表:
    sql 复制代码
        CREATE TABLE `dbtest`.`Untitled`  (
      `id` bigint NOT NULL AUTO_INCREMENT,
      `product_id` bigint NOT NULL,
      `count` int NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
    1. 订单服务通过Openfegin远程调用库存服务,然后库存服务减库存,订单服务生成订单,完成基本调用之后我们给服务添加异常。
    • 使用@GlobalTransactional 开启分布式事务,该段代码位于cloud-alibaba-seata-order-8801中,主要用于生成订单,开启分布式事务。
kotlin 复制代码
package com.ts.cloudalibabaseataorder8801.controller;

import com.ts.cloudalibabaseataorder8801.service.IOrderService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {
    @Autowired
    IOrderService orderService;

    @GetMapping("/order/create")
    @GlobalTransactional
    public String create(){
        orderService.create();
        return "生成订单";
    }
}
  • 该段代码位于cloud-alibaba-seata-order-8801项目当中。主要用于修改库存,并且模拟分布式事务中的异常情况。
java 复制代码
   @Service
public class OrderServiceImpl implements IOrderService {
   @Autowired
   OrderMapper orderMapper;
   @Autowired
   StockClient stockClient;
   @Override
   public void create() {
       stockClient.decrement(); //减库存
       int i = 1/0;  //发生异常,回滚数据
       orderMapper.create();  //创建新订单
   }
}
    1. undo_log表中的数据会用来进行数据回滚,undo_log表中的数据是临时性的,回滚之后就会被删除

4.4 总结

Seata-AT模式是Seata所支持的一种分布式事务模式,其核心逻辑还是两阶段提交的方案,本地事务先提交到本地,并写入"undo_log"中,分布式事务管理中心TM来判断整体事务是否需要发生回滚,如果需要发生回滚则本地事务分别根据"undo_log"来进行回滚。Seata的AT模式是一个相对简单且性能高的分布式事务解决方案,适用于许多传统应用场景。但在某些特定复杂场景下,可能需要考虑其他更复杂的分布式事务模式,如TCC,来满足需求。

5. Seata-XA模式(强一致性)

5.1 什么是XA协议?

XA(​eXtended Architecture,分布式事务)模式是一种用于处理分布式事务的协议和规范,可以理解为分布式事务中的RPC协议。它是X/Open公司定义的一种分布式事务处理(DTP,Distributed Transaction Processing)标准,用于协调和管理涉及多个独立资源管理器(通常是数据库或消息队列)在同一个分布式事务中访问。XA模式的目标是确保所有参与的资源都能够按照协调的方式参与到一个分布式事务中,要么都成功提交,要么都回滚,以保持数据的一致性和完整性。

XA模式的核心概念包括:

  1. 事务管理器(Transaction Manager):负责协调分布式事务的执行,跟踪各个资源管理器的事务状态,协调事务的提交和回滚。负责协调和管理服事务,事务管理器控制着全局事务,管理事务生命周期,并协调各个RM。

  2. 资源管理器(Resource Manager):事务参与者,代表各种资源,如数据库、消息队列等,负责执行事务的各个分支,以及与事务管理器通信以报告事务状态。

  3. 两阶段提交(Two-Phase Commit):XA事务遵循两阶段提交协议,首先在"准备阶段"协调各个资源管理器,以确保它们都可以安全地提交事务。然后,在"提交阶段",事务管理器通知各个资源管理器提交或回滚事务。

  4. AP:即应用程序,可以理解为使用DTP分布式事务的程序。

DTP模式定义TM和RM之间的通讯接口的规范叫做XA,简单理解为数据库提供的2PC接口协议,基于数据库的XA协议来实现的2PC又称之为XA方案。

XA模式的优点是可以确保多个资源的事务强一致性,但也存在一些缺点,包括性能开销较大和对资源管理器的支持要求。它通常用于需要高度事务一致性的分布式应用,如金融系统和企业级应用程序,但在其他情况下,可能会选择更轻量级的分布式事务处理方法。

  • 案例
  1. 应用程序(AP)持有订单库和商品库两个数据源;

  2. 应用程序(AP)通过TM通知订单库RM来创建订单和减库存,RM此时未提交事务,此时,商品和订单资源锁定;

  3. TM收到执行回复,只要有一方失败则分别向其它的RM发送事务回滚,回滚完毕则资源锁释放;

  4. TM收到执行回复,全部成功则向所有的RM发起提交事务,提交完毕,锁资源释放;

  • XA协议的痛点:

    如果一个参与全局事务的资源(数据库、消息队列)失联了(收不到分支事务结束的命令),那么它锁定的数据将一直被锁定,甚至因此而产生死锁。这是XA协议的核心痛点,也是Seata引入XA模式要重点解决的问题。

5.2 Seata事务模式

Seata定义了全局事务框架,全局事务定义为若干分支事务的整体协调:

  1. TM(Transaction Manager,事务管理者)向TC请求发起(Begin)、提交(Commit)、回滚(Rollback)全局事务;

  2. TM把代表全局事务的XID绑定到分支事务上;

  3. RM(Resource Manager,资源管理器)向TC(Transaction Coordinator,事务协调者)注册,把分支事务关联到XID代表的全局事务当中;

  4. RM把分支事务执行结果上报给TC(可选);

  5. TC发送分支提交(Branch Commit)或分支回滚(Branch Rollback)命令给RM;

Seata的全局事务处理过程分为2个阶段:

  • 执行阶段:执行分支事务,并保证执行结果满足是可以回滚的(Rollbackable)和持久化的(Durable);

  • 完成阶段:根据执行阶段结果形成决议,应用通过TM发出的全局提交或回滚请求给TC,TC命令RM驱动分支事务进行Commit或Rollback;

Seata的所谓事务模式是指:运行在Seata全局事务框架下的分支事务的行为模式,准确的讲应该叫做分支事务模式。不同的事务模式区别在于分支事务使用不同的方式达到全局事务两个阶段的目标,即回答以下2个问题:

  • 执行阶段:如何执行并保证执行结果满足是可回滚的和持久化的;

  • 完成阶段:收到TC的命令之后,如何做到分支提交或者回滚;

以AT模式为例:

  • 执行阶段:

    • 可回滚:根据SQL解析结果,记录回滚日志 undo_log;

    • 持久化:回滚日志和业务SQL在同一个本地事务中提交到数据库;

  • 完成阶段:

    • 分支提交:异步删除回滚日志记录;

    • 分支回滚:依据回滚日志进行反向补偿更新;

5.3 Seata的XA模式

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

  • 执行阶段:

    • 可回滚:业务SQL操作放在XA分支中进行,由资源对XA协议的支持来保证可回滚;

    • 持久化:XA分支完成后,执行XA Prepare,由资源对XA协议的支持来保证持久化(之后任何意外都不会造成无法回滚的情况);

  • 完成阶段:

    • 分支提交:执行XA分支的commit

    • 分支回滚:执行XA分支的rollback

5.4 为什么要在Seata中支持XA

本质上来说,Seata支持的3大事务模式:AT、TCC、Saga都是补偿型的。补偿型事务处理机制构建在事务资源之上(中间件/应用层面),事务资源本身对于分布式事务是无感知的。事务资源对分布式事务的无感知存在一个根本性的问题:无法做到真正的全局一致性,本地事务中途可能会读取到脏数据,比如:

一条库存记录在补偿型事务处理过程当中,由100扣减为50,此时,仓库管理员链接数据库,查询统计库存就看到当前的50。之后,事务由于意外回滚,库存会被补偿回滚为100,显然,仓库管理员查询统计到的50就是脏数据。所以说补偿型事务是存在中间状态的,中途可能读到脏数据。

与补偿型事务不同,XA协议要求事务资源本身提供对规范和协议的支持。因为事务资源感知并参与分布式事务处理过程,所以事务资源(数据库/消息队列)可以保证从任意视角对数据的访问有效隔离,满足全局数据一致性。

除了全局一致性这个根本价值之外,支持XA模式还有以下几个方面的好处:

  1. 业务无侵入:AT模式和XA模式都是业务无侵入的,不会给应用设计和开发带来额外负担

  2. 数据库的支持广泛:XA协议被主流数据库广泛支持,不需要额外适配即可使用;

  3. 多语言支持:因为不涉及到SQL解析,XA模式对Seata的RM的要求比较少;

  4. 易于迁移:传统的基于XA协议的应用迁移到Seata平台,使用XA模式将更加平滑;

5.5 Seata XA模式的应用

XA模式的应用主要参考官网实现,完整官网代码:​github.com/seata/seata...

  • 案例解析:

​ 从编程模型上,XA 模式与 AT 模式保持完全一致。上面案例的场景是 Seata 经典的,涉及库存、订单、账户 3 个微服务的商品订购业务。在样例中,上层编程模型与 AT 模式完全相同。只需要修改数据源代理,即可实现 XA 模式与 AT 模式之间的切换。

typescript 复制代码
@Bean("dataSource")
    public DataSource dataSource(DruidDataSource druidDataSource) {
        // DataSourceProxy for AT mode
        // return new DataSourceProxy(druidDataSource);

        // DataSourceProxyXA for XA mode
        return new DataSourceProxyXA(druidDataSource);
    }

5.6 总结

当前的技术发展阶段,分布式事务机制都无法同时满足分布式事务的一致性、可靠性、易用性和性能等诸多方面的系统设计的约束,需要使用不同的事务处理机制去满足。

Seata项目最核心的价值在于构建了一个全面解决分布式事务问题的标准化平台,基于Seata,上层应用架构可以根据实际场景的需求灵活选择合适的分布式事务解决方案。

XA模式的存在补齐了Seata在全局一致性场景下的缺口,形成了AT、TCC、Saga、XA四大事务模式的拌入,基本可以满足所有的分布式事务场景的使用需求。

6. Seata-TCC模式

6.1 什么是TCC?

TCC也是分布式事务中的二阶段提交协议,它的全部称为Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),具体含义如下:

  1. Try:对业务资源的检查并预留;

  2. Confirm:对业务处理进行提交,即Commit操作,只要try成功那么该步骤一定成功;

  3. Cancel:对业务处理进行取消(回滚),该步骤会对Try资源进行释放;

TCC是一种侵入性的分布式事务解决方案,以上三个操作都是业务系统自行实现,对业务系统有着非常大的侵入性,设计相对复杂,但是TCC的优点是完全不依赖数据库,能够实现跨数据库、跨应用资源对这些不同数据访问通过侵入式编码的方式实现一个原子操作,更好的解决了在各种复杂业务场景下的分布式事务问题。

6.2 Seata中的TCC模式

6.2.1 TCC和AT区别

AT模式基于支持本地ACID事务的关系型数据库:

  • 一阶段prepare行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录;

  • 一阶段commit行为:马上成功结束,自动异步批量清理回滚日志;

  • 二阶段rollback行为:通过回滚日志,自动生成补偿操作,完成数据回滚;

TCC模式不依赖于底层数据资源的事务支持:

  • 一阶段prepare行为:调用自定义的prepare逻辑;

  • 一阶段commit行为:调用自定义的commit逻辑;

  • 二阶段rollback行为:调用自定义的rollback逻辑;

所谓的TCC模式是指支持把自定义的分支事务纳入到全局事务管理中;

  • 特点:

    • 侵入性比较强,需要程序员自己实现相关的事务控制逻辑;

    • 整个过程基本没有锁,性能较强;

6.2.2 案例

官网:github.com/seata/seata...

上图以一个订单服务和库存服务为栗子,订单服务和库存服务都需要自己定义try、confirm和cancel方法,当事务发生提交或者回滚的时候事务协调者会调用这些方法。

7. Seata-Saga模式

7.1 什么是Saga

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

Saga模式下分布式事务通常都是由事件驱动的,各个参与者之间是异步执行的,Saga模式是一种长事务解决方案。

7.2 为什么需要Saga

AT、XA和TCC三种事务模式中所使用的微服务全部可以根据开发者的需求进行一定程度上的修改,但是在一些特殊环境下,如老系统、封闭系统(无法修改,同时没有任何分布式事务的引入),那么AT、XA、TCC模式将全部不能使用,为了解决这个问题才引入了Saga模式。如:事务参与者可能是其它公司的服务或者是遗留系统,无法改造就可以使用Saga模式。

Saga模式是Seata提供的长事务解决方案,提供了异构系统的事务统一模型。在Saga模式中,所有子业务都不在直接参与整体事务的处理(只负责本地事务的处理),而是全部交由最终调用端来负责实现,而在进行总业务逻辑处理时,某一个子业务出现问题的时候,则自动补偿全面已经成功的其它参与者,这样一阶段的正向服务调用和二阶段的服务补偿处理全部由总业务开发实现。

7.3 Saga状态机

目前Seata提供的Saga模式只能通过状态机引擎来实现,需要开发者手动进行Saga业务流程绘制,并将其转换为Json配置文件。而后在程序运行时,将根据子配置文件实现业务处理以及服务补偿处理,而想要进行Saga状态图绘制,一般需要通过Saga的状态机来实现。

基本原理:

  • 通过状态图来定义服务调用的流程并生成Json定义文件;

  • 状态图中一个节点可以调用一个服务,节点可以配置其它的补偿节点;

  • 状态图Json由状态机引擎驱动之行,当出现异常时状态引擎反向执行已经成功节点对应的补偿节点,讲其事务进行回滚;

  • 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能;

示例图:

7.4 Saga状态机设计器

参考官网:seata.io/zh-cn/docs/...

7.5 总结

Seata是针对异构系统和长事务设计的一种事务模型,其核心流程是"正向服务阶段"和"反向补偿阶段",都需要由于业务开发人员来实现。Saga的实现是基于Saga的状态机,官网也提供了可视化的状态机设置页面。由于80%的分布式事务场景都可以通过AT模式来解决,因此对于Saga模式只需要进行了解即可。

相关推荐
努力的布布15 分钟前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
PacosonSWJTU20 分钟前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
记得开心一点嘛29 分钟前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala
黄俊懿1 小时前
【深入理解SpringCloud微服务】手写实现各种限流算法——固定时间窗、滑动时间窗、令牌桶算法、漏桶算法
java·后端·算法·spring cloud·微服务·架构
2401_857439691 小时前
“衣依”服装销售平台:Spring Boot技术应用与优化
spring boot·后端·mfc
Jerry.ZZZ2 小时前
系统设计,如何设计一个秒杀功能
后端
车载诊断技术3 小时前
什么是汽车中的SDK?
网络·架构·汽车·soa·电子电器架构
企业码道刘某3 小时前
EasyExcel 大数据量导入导出解决方案进阶
java·后端·mybatis
九圣残炎3 小时前
【springboot】简易模块化开发项目整合Redis
spring boot·redis·后端