八、Spring Cloud Alibaba-seata分布式事务

news/2025/1/3 3:49:14/

一、引言

1、事务简介
事务(Transaction)是访问并可能更新数据中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语向组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

  • 原子性(atomicity): 事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
  • 一致性(consistency): 事务必须是使数据库从一个一致性状态变到另一个一致性状态,事务的中间状态不能被观察到的。
  • 隔离性(isolation): 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级: 读未提交(read uncommited)、读已提交(read commited,解决读)、可重复读(repeatable read,解决虚读)、串行化(serializable,解决幻读)。
  • 持久性(durabilty): 持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响任何。

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

2、本地事务
@Transational
大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务(Local Transacton。本地事务的ACID特性是数据库直接提供支持。

3、分布式事务
多个数据库时,服务与服务之间,数据库与数据库之间互相调用时,不属于同一个进程,用@Transational本地事务不能解决,得使用分布式事务。

4、常见的分布式事务解决方案

  • seata
  • 消息队列
  • saga
  • XA
    他们共同点:都是“两阶段(2PC)”,指完成整个分布式事务,划分成两个步骤完成。两阶段后面会详细说。

二、什么是seata

Seata 是一款阿里巴巴开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式。

官网:http://seata.io/zh-cn/index.html
源码:https://github.com/seata/seata
官方demo:https://github.com/seata/seata-samples

1.1 seata的三大角色

TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。

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

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

1.2 分布式事务的生命周期

在Seata 中,一个分布式事务的生命周期如下:
在这里插入图片描述

  1. TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。
  2. RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的XID 进行关联。
  3. TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
  4. TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。

1.3 Seata 基本运行原理

通过XID作为全局事务的编号,存入数据库中。事务完成后会清楚表中对应数据。对应这上面的生命周期。

  1. global_table 表存储全局事务信息,xid主键。每一个@GlobalTransactional就会生成一个xid。
  2. branch_table 表存储事务分支信息,branch_id为主键,xid为父级主键。每执行一次数据库操作就会生成一条分支信息。
  3. lock_table 锁表信息,锁住当前事务对应的数据条,完成事务与事务之间的隔离。记录全局锁,当前持有全局事务ID、分支事务ID、数据库主键。
  4. undo_log会存储每次分支运行前后的数据,存储了branch_id和xid,以及rollback_info运行前后的数据信息(beforeImage执行前数据,afterImage为执行的数据,数据信息是sqlType以及值,可以拼接成SQL的数据信息,根据该表完成回滚需执行的SQL)。
  5. distributed_lock Seata 0.7.1 及以上版本才有 。distributed_lock lock_table 都是 Seata 分布式事务框架中用于协调分布式事务的表,但是它们的作用不同。distributed_lock 表是 Seata 分布式事务框架中用于实现分布式锁的表,主要用于实现分布式事务的并发控制,保证分布式事务的原子性和一致性。当一个事务需要锁定一个资源时,会在 distributed_lock 表中插入一条记录,当事务释放资源时,会删除对应的记录。
    lock_table 表则是用于记录分布式事务的全局锁定信息,用于实现分布式事务的一致性。当一个分布式事务在执行时,需要锁定某个资源时,会在 lock_table 表中插入一条记录,并将对应资源的状态设置为锁定状态。当分布式事务完成时,会释放所有相关资源,同时将 lock_table 表中对应的记录删除,将资源状态设置为可用状态。
    总之,distributed_lock 表用于分布式事务的并发控制,lock_table 表则用于分布式事务的全局锁定信息记录和管理。
    默认的表如下:
    在这里插入图片描述

三、seata二阶段提交协议

一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

  • 一阶段 prepare 行为。
  • 二阶段 commit 或 rollback 行为。

全阶段由一个协调者进行总控制,其他为参与者,一个协调者负责控制与其他参与者的通信。
一阶段流程(预处理阶段):

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

二阶段(提交/回滚阶段):

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

在执行 Prepare 步骤过程中,如果某些参与者执行事务失败、岩机或与协调者之间的网络中断,那么协调者就无法收到所有参与者的ack:YES 响应,或者某个参与者返回了 NO 响应此时,协调者就会进入回退流程,对事务进行回退。(将 Commt 请求替换为红色的 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模式

AT(auto transcation)——自动的事务,是一种无侵入的分布式事务解决方案。
seata框架实现AT模式,也是AT模式的一个典型的代表。
在AT 模式下,用户只需关注自己的“业务 SQL",用户的“业务 SQL"作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

一阶段:
业务数据和回滚日志记录在同一事务中提交,释放本地锁和连接资源。核心在于对业务SQL进行解析,转换成undo log,并入库。

在一阶段,Seata 会拦截”业务 SQL”,首先解析 SQL 语义,找到业务 SQL"要更新的业务数据,在业务数据被更新前,将其保存成"before image",然后执行“业务 SQL"更新业务数据,在业务数据更新之后,再将其保存成”after image",最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
在这里插入图片描述
二阶段提交:
分布式事务操作成功,则TC通知RM异步删除undo log。
二阶段如果是提交的话,因为“业务 SQL"在一阶段已经提交至数据库,所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
在这里插入图片描述
**二阶段回滚:**分布式事务操作失败,TM会向TC发起回滚请求,RM收到协调器TC发来的回滚请求,通过XID和Branch ID找到相应的回滚日志记录,通过回滚记录反向更新SQL并执行,以完成分支的回滚。

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

在这里插入图片描述

五、TCC模式

TCC模式需要用户根据自己的业务场景实现Try、Comfirm和Cancel三个操作;事务发起方在一阶段执行Try方式,在二阶段提交执行Confirm方法,二阶段回滚执行Cancel方法。

  • 侵入性比较强,并且得自己实现相关事务控制的逻辑。
  • 在整个过程中基本没有锁,性能更强。
    在这里插入图片描述

六、seata server快速开始

seata db + nacos方式部署高可用集群模式。

6.1 部署指南

官网:http://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html

Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
server端存储模式支持三种:

  • file:(默认)单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高。路径seata\bin\sessionStore\root.data,默认方式启动时会创建该文件。
  • db:(5.7+)高性能模式,全是事务会话信息通过db共享,性能相应差一些。微服务共享,使用该模式。
  • redis:seata-server 1.3及以上版本支持,性能较高,存在事务信息丢失风险。

6.2 服务端TC搭建

下载地址:https://github.com/seata/seata/releases/tag
资源目录:
https://github.com/seata/seata/tree/master/script

  • client:存放client端sql脚本 (包含 undo_log表) ,参数配置。
  • config-center:各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件。
  • server:server端数据库脚本 (包含 lock_table、branch_table 与 global_table) 及各个容器配置

在这里插入图片描述

配置db模式:
(1)修改配置文件信息。
路径:seata\conf\file.conf

store {## store mode: file、db、redis### 修改为db模式mode = "db"## database store propertydb {## 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"## 修改数据库信息url = "jdbc:mysql://127.0.0.1:3306/seata_test"user = "root"password = "123456"minConn = 5maxConn = 30globalTable = "global_table"branchTable = "branch_table"lockTable = "lock_table"queryLimit = 100maxWait = 5000}}

(2)创建数据库seata_test并执行对应SQL

数据库表脚本在资源目录地址中:

https://github.com/seata/seata/blob/master/script/server/db/mysql.sql

在对应的库执行一下里面的SQL。
一共有4个表,根据版本不一样,有的只有3个表。
在这里插入图片描述

我们将https://github.com/seata/seata/blob/master/script的资源列表下载下来,放入到seata-server文件下,后续会用到。

6.3 更改注册中心和配置中心

将服务端TC注册到nacos,达到高可用模式。
配置seata\conf\registry.conf文件配置成nacos。

registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa# 注册中心。配置成naocs,默认filetype = "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# 配置中心也修改为nacostype = "nacos"nacos {serverAddr = "127.0.0.1:8848"namespace = ""group = "SEATA_GROUP"username = "nacos"password = "nacos"}}

6.4 修改资源列表script\config-center\conf.txt文件信息

# 配置成db
store.mode=db#修改DB相关配置
store.db.url=jdbc:mysql://127.0.0.1:3306/seata_test?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456#service.vgroupMapping.default_tx_group=default
#事务分组,异地机房停电容错
#default_tx_group可以自定义,比如(gaunghzou、shanghai),对应的#client也要去设置
#1、客户端需要去设置seata.service.vgroup-#mapping.my_test_tx_group=guangzhou,指定对应的分组。
#根据guangzhou不可用,可以快速切换成shanghai
#2default必须等于注册中心
即(seata\conf\registry.conf中registry配置的cluster)cluster = "default"的值
service.vgroupMapping.guangzhou=default#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

6.5 将修改后的信息推送到nacos

使用seata\script\config-center\nacos文件下的脚本进行推送到nacos配置中心。
.sh在windows下不能运行,安装git即可。
在这里插入图片描述
我使用的是后面nacos-config-interactive.sh的方式。

也可以在启动时指定相关信息使用第一种标本推送。

sh $(SEATAPATH)/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA GROUP-t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca

参数说明:
-h: host,默认值localhost
-p: port,默认值8848
-g:配置分组,默认值为SEATA GROUP
-t:租户信息,对应Nacos 的命名空间ID字段,默认值为空

在这里插入图片描述
推送成功后如图:
在这里插入图片描述

6.6 启动Seata server

源码启动: 执行server模块下ioseata.server.Server,java的main方法
命令启动: bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test

参数说明:
在这里插入图片描述
linux下集群部署启动seata server,指定端口:

bin/seata-server.sh -p 8092

本地windows直接运行seata\bin\seata-server.bat,默认8091,启动成功后,就能在nacos界面看到seata服务。
在这里插入图片描述

七、seata client端快速开始

学习地址:
http://seata.io/zh-cn/docs/user/quickstart.html

7.1 添加pom依赖

<!--  seata,需放入用到的所有微服务中-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

7.2 各微服务对应数据库中添加undo_log表

用于回滚的日志表。

-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
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;

7.3 配置文件配置

seata:#配置  tx-service-group事务分组tx-service-group: guangzhou#配置seata的注册中心(nacos模式),告诉Seata client 怎么去访问seata server (TC)registry:type: nacosnacos:application: seata-server #seata server服务名server-addr: 127.0.0.1:8848 #seata server所在的服务地址username: nacospassword: nacosgroup: SEATA_GROUP#配置seata的配置中心(nacos模式)config:type: nacosnacos:server-addr: 127.0.0.1:8848 #seata server所在的服务地址username: nacospassword: nacosgroup: SEATA_GROUP

此处和server端一直,即6.4中说明的。
#1、客户端需要去设置seata.service.vgroup- guangzhou#mapping.my_test_tx_group=guangzhou,指定对应的分组。

7.4 在接口中使用@GlobalTransactional注解即可

@GlobalTransactional
public String add(Order order){//入库 order 数据库+1orderMapper.insert(order);//库存 stock 数据库-1String reduce = stockFeignService.reduce();// int a = 1/0; 进行异常测试两个库中数据的回滚return "openFeign远程调用:"+reduce + "---"+product;
}

http://www.ppmy.cn/news/90548.html

相关文章

数据库提权

数据库提权的前提就是得到数据库的账号密码。在webshell或本地进行提权。 数据库提权分为四步&#xff1a; 1.服务探针&#xff0c;探测出数据库的类型&#xff08;端口扫描等&#xff09; 2.信息搜集&#xff0c;就是获取到数据库的账号密码。权限要高。 读取数据库密码的…

基于ROS2的costmap中Obstacle Layer中对障碍物信息的增加与删除机制的方案调研。

文章目录 1.背景2.目标3. 障碍物信息添加方式发送数据的数据结构与接收数据的数据结构 4. 障碍物清理机制4.1 参数过滤障碍物4.2 时间过滤障碍物4.3 优化光追算法过滤障碍物4.4 障碍物跟踪过滤障碍物4.4.1 无语义动态障碍物跟踪4.4.2 语义障碍物分层4.4.3 障碍物实例分割与类别…

使用CNN-LSTM来预测锂离子电池健康状态SOH(附代码)

对于电动汽车而言&#xff0c;动力锂电池的健康状态(State of Health,SOH)估算方法是电池管理系统中非常重要的一个方面。准确估计锂电池老化状态并预测电池剩余寿命对于电动汽车稳定安全运行有着重要的意义。借助数据驱动方法的思想&#xff0c;本文对锂离子电池寿命历史数据进…

C++ Primer第五版_第十八章习题答案(11~20)

文章目录 练习18.11练习18.12练习18.13练习18.14练习18.15练习18.16练习18.17练习18.18练习18.19练习18.20 练习18.11 为什么 what 函数不应该抛出异常&#xff1f; what中如果抛出异常&#xff0c;需要try catch捕获&#xff0c;再调用what&#xff0c;一直循环&#xff0c;直…

霍尔电流传感器的注意事项及其在直流列头柜中的应用

安科瑞虞佳豪 霍尔电流传感器​注意事项 &#xff08;1&#xff09;电流传感器必须根据被测电流的额定有效值适当选用不同的规格的产品。被测电流长时间超额&#xff0c;会损坏末极功放管&#xff08;指磁补偿式&#xff09;&#xff0c;一般情况下&#xff0c;2倍的过载电流…

“多杆合一”降本增效——数字孪生智慧灯杆

随着智慧城市建设的不断深入&#xff0c;智慧灯杆作为城市基础设施的重要组成部分&#xff0c;正在成为城市智能化和绿色化的重要手段之一。 图扑智慧灯杆系统在城市道路照明领域引入信息化手段&#xff0c;通过构建路灯物联网&#xff0c;实现了现代化的路灯按需维修和按需照…

【操作系统真象还原】第4章:保护模式入门(4.4~4.5节)

目录 4.4 处理器微架构简介 4.4.1 流水线 4.4.2 乱序执行 4.4.3 缓存 4.4.4 分支预测 4.5 使用远跳转指令清空流水线&#xff0c;更新段描述符缓冲寄存器 4.6 保护模式之内存段的保护 4.6.1 向段寄存器加载选择子时的保护 4.6.2 代码段和数据段的保护 4.6.3 栈段的保…

Rust 入门教程

文章目录 前言1. 使用cargo创建项目2. 编程语言类型1. 整数2. 浮点数3. 字符 3. 常量与不可变变量的区别4. 重影&#xff08;Shadowing&#xff09;5. 两个重要的泛型类型6. 常见的内存管理方式7. 如何理解生命周期&#xff1f;8. 条件语句1. if实例2. while循环实例3. for循环…