Seata分布式事务

发布时间 2023-04-17 19:46:18作者: 西东怪

Seata

目录旁边可以查询具体的目录结构和跳转

一.分布式事务

	1.原子性(atomicity):个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么
都不做。

 	2.一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态,事务
的中间状态不能被观察到的。

     3.隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数
据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别:读
未提交(read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable
read,解决虚读)、串行化(serializable,解决幻读)。

    4.持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库
中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

    任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务,及时不能
都很好的满足,也要考虑支持到什么程度。

1.本地事务@Transational

本地事务(数据库事务)简单理解就是整个服务只操作单一数据源,比如MySQL数据库。

一次事务可以理解为数据库执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。场景:点击一个“按钮”,触发一个方法,执行了有限多次的“增、删、改”操作。

本地事务就是用关系数据库来控制事务,关系数据库通常都具有ACID特性,传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。(我们所熟知的MySQL事务)

本地事务

2.分布式事务

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,以上是百度百科的解释。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性

(1)服务化:在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。这里强调的是多个系统通过网络协同完成一个事务的过程,并不强调多个系统访问了不同的数据库,即使多个系统访问的是同一个数据库也是分布式事务,如下图:

分布式事务1

分布式事务3

(2)跨库事务:一个应用程序使用了多个数据源连接了不同的数据库,当一次事务需要操作多个数据源,此时也属于分布式事务**,当系统作了数据库拆分后会出现此种情况。

分布式事务2

上面两种分布式事务表现形式第一种用的最多.

3.常见分布式事务解决方案

1、seata 阿里分布式事务框架(AT)

2、消息队列(TCC)

3、saga

4、XA

他们有一个共同点,都是“两阶段(2PC)”。“两阶段”是指完成整个分布式事务,划分成两个步骤完成。实际上,这四种常见的分布式事务解决方案,分别对应着分布式事务的四种模式:AT、TCC、Saga、XA;四种分布式事务模式,都有各自的理论基础,分别在不同的时间被提出;每种模式都有它的适用场景,同样每个模式也都诞生有各自的代表产品;而这些代表产品,可能就是我们常见的(全局事务、基于可靠消息、最大努力通知、TCC)。今天,我们会分别来看4种模式(AT、TCC、Saga、XA)的分布式事务实现。

在看具体实现之前,先讲下分布式事务的理论基础。

2PC两阶段提交协议:

2PC(两阶段提交,Two-Phase Commit)

顾名思义,分为两个阶段:Prepare 和 Commit

Prepare:提交事务请求

基本流程如下图:

提交事务请求

  1. 询问 协调者向所有参与者发送事务请求,询问是否可执行事务操作,然后等待各个参与者的响应。

  2. 执行 各个参与者接收到协调者事务请求后,执行事务操作(例如更新一个关系型数据库表中的记录),并将 Undo(回滚日志) 和 Redo(重做日志) 信息记录事务日志中。

  3. 响应 如果参与者成功执行了事务并写入 Undo 和 Redo 信息,则向协调者返回 YES 响应,否则返回 NO 响应。当然,参与者也可能宕机,从而不会返回响应。

    Redo Log(重做日志)是为了系统崩溃之后恢复数据用的,让数据库照着日志,把没做好的事情重做一遍。有了Redo Log,就可以保证即使数据库发崩溃重启后,之前提交的记录都不会丢失,这个能力称为 crash-safe。
    
    Undo Log(回滚日志)是为了回滚用的。在事务提交之前就开始写数据,万一事务到最后又打算不提交了,要回滚,或者系统崩溃了,这些提前写入的数据就变成了脏数据,这时候就必须用Undo Log恢复了。
    
Commit:执行事务提交及事务中断

执行事务提交分为两种情况,正常提交和回退。

正常提交事务

流程如下图:

执行事务请求

  1. commit 请求 协调者向所有参与者发送 Commit 请求。

  2. 事务提交 参与者收到 Commit 请求后,执行事务提交,提交完成后释放事务执行期占用的所有资源。

  3. 反馈结果 参与者执行事务提交后向协调者发送 Ack 响应。

  4. 完成事务 接收到所有参与者的 Ack 响应后,完成事务提交。

中断事务

在执行 Prepare 步骤过程中,如果某些参与者执行事务失败、宕机或与协调者之间的网络中断,那么协调者就无法收到所有参与者的 YES 响应,或者某个参与者返回了 No 响应,此时,协调者就会进入回退流程,对事务进行回退。流程如下图红色部分(将 Commit 请求替换为红色的 Rollback 请求):

中断事务

  1. rollback 请求 协调者向所有参与者发送 Rollback 请求。

  2. 事务回滚 参与者收到 Rollback 后,使用 Prepare 阶段的 Undo 日志执行事务回滚,完成后释放事务执行期占用的所有资源。

  3. 反馈结果 参与者执行事务回滚后向协调者发送 Ack 响应。

  4. 中断事务 接收到所有参与者的 Ack 响应后,完成事务中断。

2PC 的问题
  1. 同步阻塞 参与者在等待协调者的指令时,其实是在等待其他参与者的响应,在此过程中,参与者是无法进行其他操作的,也就是阻塞了其运行。 倘若参与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去。

  2. 单点 在 2PC 中,一切请求都来自协调者,所以协调者的地位是至关重要的,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源。如果协调者也是分布式,使用选主方式提供服务,那么在一个协调者挂掉后,可以选取另一个协调者继续后续的服务,可以解决单点问题。但是,新协调者无法知道上一个事务的全部状态信息(例如已等待 Prepare 响应的时长等),所以也无法顺利处理上一个事务。

  3. 数据不一致 Commit 事务过程中 Commit 请求/Rollback 请求可能因为协调者宕机或协调者与参与者网络问题丢失,那么就导致了部分参与者没有收到 Commit/Rollback 请求,而其他参与者则正常收到执行了 Commit/Rollback 操作,没有收到请求的参与者则继续阻塞。这时,参与者之间的数据就不再一致了。当参与者执行 Commit/Rollback 后会向协调者发送 Ack,然而协调者不论是否收到所有的参与者的 Ack,该事务也不会再有其他补救措施了,协调者能做的也就是等待超时后像事务发起者返回一个“我不确定该事务是否成功”。

  4. 环境可靠性依赖 协调者 Prepare 请求发出后,等待响应,然而如果有参与者宕机或与协调者之间的网络中断,都会导致协调者无法收到所有参与者的响应,那么在 2PC 中,协调者会等待一定时间,然后超时后,会触发事务中断,在这个过程中,协调者和所有其他参与者都是出于阻塞的。这种机制对网络问题常见的现实环境来说太苛刻了。

AT模式(auto transcation)

AT 模式如何做到对业务的无侵入 :

一阶段:

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

AT1

二阶段提交:

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

二阶段回滚:

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
AT3

TCC 模式

  1. 侵入性比较强, 并且得自己实现相关事务控制逻辑
  2. 在整个过程基本没有锁,性能更强

TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶

段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。

TCC 三个方法描述:

Try:资源的检测和预留;

Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;

Cancel:预留资源释放

saga模式

Saga 理论出自 Hector & Kenneth 1987发表的论文 Sagas。

saga模式的实现,是长事务解决方案

Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正

补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式

事务提交。如果任何一个正向操作执行失败,那么分布式事务会退回去执行前面各参与者的逆向回滚

操作,回滚已提交的参与者,使分布式事务回到初始状态。

Saga 正向服务与补偿服务也需要业务开发者实现。因此是业务入侵的。

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

务解决方案。

Saga 模式使用场景

Saga 模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地

事务,无锁、长流程情况下可以保证性能。

事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,

可以使用 Saga 模式。

Saga模式的优势是:

1.一阶段提交本地数据库事务,无锁,高性能;

2.参与者可以采用事务驱动异步执行,高吞吐;

3.补偿服务即正向服务的“反向”,易于理解,易于实现;

缺点:

Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔

离性。后续会讲到对于缺乏隔离性的应对措施。与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持:

1.空补偿:逆向操作早于正向操作时;

2.防悬挂控制:空补偿后要拒绝正向操作

3.幂等

XA模式

XA是X/Open DTP组织(X/Open DTP group)定义的两阶段提交协议,XA被许多数据库(如

Oracle、DB2、SQL Server、MySQL)和中间件等工具(如CICS 和 Tuxedo)本地支持 。

X/Open DTP模型(1994)包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)。

XA接口函数由数据库厂商提供。XA规范的基础是两阶段提交协议2PC。

JTA(Java Transaction API) 是Java实现的XA规范的增强版 接口。

在XA模式下,需要有一个[全局]协调器,每一个数据库事务完成后,进行第一阶段预提交,并通知协

调器,把结果给协调器。协调器等所有分支事务操作完成、都预提交后,进行第二步;第二步:协调

器通知每个数据库进行逐个commit/rollback。

其中,这个全局协调器就是XA模型中的TM角色,每个分支事务各自的数据库就是RM。

MySQL 提供的XA实现(https://dev.mysql.com/doc/refman/5.7/en/xa.html

XA模式下的 开源框架有atomikos,其开发公司也有商业版本。

XA模式缺点:事务粒度大。高并发下,系统可用性低。因此很少使用。

四种模式分析与总结

(AT、TCC、Saga、XA)模式分析

四种分布式事务模式,分别在不同的时间被提出,每种模式都有它的适用场景

​ 1.AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学

习成本。

​ 2.TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。

​ 3.Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,

Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成

层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供

TCC 要求的接口,也可以使用 Saga 模式。

​ 4.XA模式是分布式强一致性的解决方案,但性能低而使用较少

总结

分布式事务本身就是一个技术难题,业务中具体使用哪种方案还是需要不同的业务特点自行选择,但

是我们也会发现,分布式事务会大大的提高流程的复杂度,会带来很多额外的开销工作,「代码量上去了,业务复杂了,性能下跌了】

所以,当我们真实开发的过程中,能不使用分布式事务就不使用。

二.分布式Seata

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global Transaction Service 全局事务服务)

2.1 Seata的三大角色

在 Seata 的架构中,一共有三个角色:

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端

2.2 Seat的AT模式

AT模式的核心是对业务无侵入,是一种改进后的两阶段提交,其设计思路如图

第一阶段

业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。核心在于对业务sql进行解析,转换成undolog,并同时入库.

Seat的AT1

第二阶段

分布式事务操作成功,则TC通知RM异步删除undolog

Seat的AT2

分布式事务操作失败,TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。

Seat的AT3

三.Seata的使用

3.1快速开始

1.Seata Server(TC)环境搭建

步骤一:下载安装包(1.5.2)

https://github.com/seata/seata/releases

步骤二:修改数据源配置,选择数据库DB(>=mysql5.7)

1.config/application.yml

mode: db
db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3307/seata?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&userSSL=false&serverTimezone=GMT%2B8
      user: root
      password: 123456789
      min-conn: 5
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 100
      max-wait: 5000

步骤三:创建本地数据库seata

将script/server/db/mysql.sql在seata数据库下执行

步骤四:启动

bin/seata-server.bat

2.Seata配置nacos

Seata的事务注册到nacos中,实现负载均衡等

修改application.yml(application.example.yml中有模板)

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""
      data-id: seataServer.properties
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata  #命名空间
      cluster: default
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: "
3.将配置导入到nacos注册中心

1.修改script/config-center/config.txt

#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
store.publicKey=

#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you #can remove the configuration block.
#store.file.maxBranchSessionSize=16384
#store.file.maxGlobalSessionSize=512
#store.file.fileWriteBufferCacheSize=16384
#store.file.flushDiskMode=async
#store.file.sessionReloadReadSize=100

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3307/seata?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&userSSL=false&serverTimezone=GMT%2B8
store.db.user=root
store.db.password=123456789
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

2.seata时运行script/config-center/nacos/nacos-config.sh脚本老时失败,整了一下午原因是nacos开启了认证模式,因此运行脚本时需要指定nacos用户名和密码

fi
if [ -z ${username} ]; then
    username="nacos"
fi
if [ -z ${password} ]; then
    password="nacos"
fi

3.在nacos-config.sh目录下打开{Git Bash Here}

sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t seata -u nacos -w nacos

参数详情:命令解析:-h -p 指定nacos的端口地址;-g 指定配置的分组,注意,是配置的分组;-t 指定命名空间id; -u -w指定nacos的用户名和密码,同样,这里开启了nacos注册和配置认证的才需要指定。按回车等待配置导入成功。

第二方法:(建议此方法)

将config.txt复制到nacos中

Seata-nacos配置

四.Seata在idea

1.依赖

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

配置事务分组, 要与客户端配置的事务分组一致

service.vgroupMapping.default_tx_group=default
事务分组:  异地机房停电容错机制
default_tx_group可以自定义 比如(guangzhou、shanghai...)对应的client也要去设置

default   必须要等于 appcliation.yml  cluster = "default"

yml配置:
seata:
  tx-service-group: default_tx_group #服务事务分组
  registry:
    type: nacos
    nacos:
      cluster: default 

2.yml配置

# 数据源
spring:
  datasource:
    username: root
    password: 123456789
    url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #初始化时运行sql脚本
    schema: classpath:sql/schema.sql
    initialization-mode: never
  application:
    name: alibaba-order-seata
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos

seata:
  tx-service-group: default_tx_group #服务事务分组
  registry:
    #配置SeaTa的注册中心,告诉seata怎么去访问seata server服务
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      application: seata-server #默认为seata-server 无修改可配可以不配
      username: nacos
      password: nacos
      group: SEATA_GROUP  #默认为SEATA_GROUP 无修改可配可以不配
      namespace: seata
      cluster: default 
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      group: SEATA_GROUP
      namespace: seata
      data-id: seataServer.properties

#设置mybatis
mybatis:
  mapper-locations: classpath:com/tulingxueyuan/order/mapper/*Mapper.xml
  #config-location: classpath:mybatis-config.xml
  typeAliasesPackage: com.tulingxueyuan.order.pojo
  configuration:
    mapUnderscoreToCamelCase: true
server:
  port: 9072

3.微服务发起者(TM 方)需要添加@GlobalTransactional

	@GlobalTransactional
    @Override
    public Order create(Order order) {
        // 插入能否成功?
        orderMapper.insert(order);  //对第一个数据库修改
        // 扣减库存 能否成功?
        stockService.reduct(order.getProductId()); //第二个
        // 异常
        int a=1/0;

        return order;
    }

4.运行原理

seata运行原理

一个全局事务就是一个@GlobalTransactional对于一个TM

里面的多个调用的微服务都会有一个RM对于,通过XID与全局事务挂钩。

① 发起方 TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成唯一的全局事务标识 XID,该 XID 在后续事务的服务调用链路的上下文传播(通过Aop实现))
② RM 向 TC 注册分支事务,汇报资源准备状况,并与 XID 进行绑定(Branch分支事务指分布式事务中每个独立的本地局部事务)
③ TM 向 TC 发起 XID 下的所有分支事务的全局提交或回滚请求(事务一阶段结束)
④ TC 汇总事务信息,决定分布式事务是提交还是回滚;
⑤ TC 通知所有 RM 提交/回滚 资源,事务二阶段结束;

事务注册

Seat依赖和配置综合

#service.vgroupMapping.default_tx_group=default
#事务分组:  异地机房停电容错机制
#default_tx_group可以自定义 比如(guangzhou、shanghai...)对应的client也要去设置

#default   必须要等于 appcliation.yml  cluster = "default"

#yml配置:
seata:
  tx-service-group: default_tx_group #服务事务分组
  registry:
    #配置SeaTa的注册中心,告诉seata怎么去访问seata server服务
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      application: seata-server #默认为seata-server 无修改可配可以不配
      username: nacos
      password: nacos
      group: SEATA_GROUP  #默认为SEATA_GROUP 无修改可配可以不配
      namespace: seata
      cluster: default 
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      group: SEATA_GROUP
      namespace: seata
      data-id: seataServer.properties

依赖

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