分布式事务简介
基础概念:事务ACID
- A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失 败的情况。
- C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。比如:张三向李四转100元, 转账前和转账后的数据是正确状态这叫一致性,如果出现张三转出100元,李四账户没有增加100元这就出现了数 据错误,就没有达到一致性。
- I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事 务不能看到其他事务运行过程的中间状态。通过配置事务隔离级别可以避脏读、重复读等问题。
- D(Durability):持久性,事务完成之后,该事务对数据的更改会被持久化到数据库,且不会被回滚。
事务
-
本地事务:同一个数据库和服务器,称之为本地事务
在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。 -
分布式事务
事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应用,分布式事务需要保证这些操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
举例:
分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操 作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务,例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。
其实只要涉及到操作多个数据源,就可能会产生事务问题,在实际开发中我们要尽量避免这种问题的出现,如果避免不了,就需要进行解决,在微服务系统架构中,目前比较好,比较常用的解决方案就是Seata。
分布式事务理论
随着互联化的蔓延,各种项目都逐渐向分布式服务做转换。如今微服务已经普遍存在,本地事务已经无法满足分布式的要求,由此分布式事务问题诞生。 分布式事务被称为世界性的难题,目前分布式事务存在两大理论依据:CAP定律 BASE理论。
CAP定律
这个定理的内容是指的是在一个分布式系统中、Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
-
一致性(C)
在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本) -
可用性(A)
在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性) -
分区容错性(P)
以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择
CAP是无法同时存在的
例子
-
当库存服务将库存之后,需要将数据同步到其他服务上,这是为了保证数据一致性C,但是网络是不可靠的,所以系统需要保证分区容错性P,也就是必须容忍网络所带来的一些问题,此时如果想要保证C那么就必须放弃A,也就是说在保证C的情况下,就必须舍弃A,也就是CP无法保证高可用
-
如果为了保证A,高可用的情况下,也就是必须在限定时间内给出响应,同样由于网络不可靠P,订单服务有可能无法拿到最新的数据,但是也要给用户做出响应,那么也就无法保证C一致性。所以AP是无法保证强一致性的
-
如果想要保证CA,也就是高可用和一执性,必须保证网络良好才能实现,也就是说需要将库存、订单、用户放在一起,但是这种情况也就丧失了P这个保证,这个时候系统就不是分布式系统了
-
总结:在分布式系统中,p是必然存在的,所有只能在C和A之间进行取舍,在这种条件下诞生了BASE理论
BASE理论
BASE是Basically Available(基本可用)、soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网分布式实践总结,是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性
-
基本可用
分布式系统在出现不可预知故障的时候,允许损失部分可用性------注意,这绝不等价于可用。例如:
(1)响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒
(2)系统功能上的损失:正常情况下,在电商网站购物时,消费者几乎能够顺利完成每笔订单,但是在一些大促时,由于流量激增,为了保证购物系统的稳定性,部分消费者可能会被引导到一个降级页面 -
软状态:(中间状态且不影响系统可用性,数据同步存在延时)
软状态指的是,允许系统中的数据存在中间状态,并且该状态的存在不会影响系统整体的可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时
最终一致性
最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能达到一个一致的状态。因此最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要保证系统数据的强一致性
**基本可用:**保证核心服务是可以使用的,至于其他的服务可以适当的降低响应时间,甚至是服务降级
**软状态:**存在中间状态,不影响整体系统使用,数据同步存在延时
**最终一致性:**再过了流量高峰期以后,经过一段时间的同步,保持各服务数据的一致
Seata简介
分布式事务解决方案
2PC 两阶段提交协议,是将整个事务流程分为两个阶段,P是指准备阶段,C是指提交阶段。
- 准备阶段(Prepare phase)
- 提交阶段(ComIMit phase)
举例:比如说男女朋友相亲去吃饭,店老板要求,先付钱在吃饭,这时双方提出AA,也就是说双方都付钱,才能落座吃饭,但是只要两个人中有一个人不统一付款就不能落座吃饭。
- 准备阶段:老板要求男方付款,男方付款;要求女方付款,女方付款
- 提交阶段:老板出餐,两人纷纷落座
此例子就形成了一个分布式事务,如果男女双方中有一个人拒绝付款,那么老板就不会出餐,并且把已收取的钱原路退回。
整个事务过程是由事务管理器(TM)和参与者(RM)组成的。店老板就是事务管理器,男女双方就是参与者(RM),事务管理器决策整个分布式事务在计算机中关系数据库支持的两阶段提交协议:
-
准备阶段(Prepare phase):事务管理器给每个参与者发送Prepare消息,每个数据库参与者在本地执行事务,并写入本地的Undo/redo日志,此时事务没提交。
(undo日志记录修改前的数据,用于数据库的回滚,redo日志是记录修改后的数据,用于提交事务后写入数据文件) -
提交阶段(Commit phase):如果事务管理器收到了参与者的执行失败或超时消息时,直接给每个参与者(RM)发送回滚(Rollback)消息;反之,发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或回滚操作,并释放事务处理过程中的使用资源。
具体步骤图例:
Seata
官网:https://seata.io/zh-cn/docs/overview/what-is-seata.html
概念:Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造了一站式的分布式事务解决方案
在微服系统中,对应业务被对应的拆分成独立的模块,在官方提供的架构图中,我们可以看出当前是三个服务:
- 仓储服务:对给定的商品进行增删操作记录数量
- 订单服务:根据采购者的需求创建订单
- 账户服务:从用户账户中扣除余额、积分等
在这套架构中,用户下单购买商品的业务,就需要三个服务来完成,每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题就没办法保证,Seata就是来进行解决这种问题的解决方案。
Seata术语
官网地址:https://seata.io/zh-cn/docs/overview/terminology.html
要了解Seata,首先我们要了解一下Seata的几个关键的概念:
TC(Transaction Coordinator)-事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
(Seata的服务端)
TM(Transaction Manager)-事务管理器(发起者,同时也是RM的一种)
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
(@GlobalTransactional)
RM(Resource Manager)- 资源管理器(每个参与事务的微服务)
管理分支事务处理器的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
(调用的资源)
(TM/RM 参与事务的数据库/表,TM也可以变成被调用的资源)
Seata-Server下载
官方下载地址:https://github.com/seata/seata/releases
解压
Seata-Server配置
先配置registry.conf配置文件,修改Seata的注册中心和配置中心为Nacos
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 = ""
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 = "nacos"
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的存储模式,修改file.conf文件,把Seata的默认存储模式修改为数据库"DB",同时需要配置JDBC
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## 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://localhost:3306/seata?useUnicode=true&characterEncoding=UTF-8"
user = "root"
password = "root"
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
}
}
启动
启动步骤为,先启动nacos然后在启动Seata-Server
启动Seata-Server的方式非常简单,直接双击此文件即可:seata-server-1.4.2\bin\seata-server.bat
启动完成效果
然后在nacos控制台上就可以看到Seata-Server
Seata Server(TC)环境搭建详解
Server端存储模式(store.mode)支持三种:
- file:单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)
- DB:高可用模式,全局事务会话信息通过DB共享,相对性能差一些
- redis:Seata-Server1.3及以上版本支持,性能较高,存在事务信息丢失风险,需要配合实际场景使用。
具体操作
修改Seata-Server模式为DB高可用模式
找到以下对应的db配置,要修改其中的jdbc连接,以及要注意其中涉及到了三个表,分别是global_table,branch_table,lock_table分别是全局事务会话表,分支事务会话表,锁数据表;
建表语句地址:https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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;
-- the table to store BranchSession data
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;
-- the table to store lock data
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);
重启Seata即可生效。
Seata配置Nacos注册中心和配置中心
Seata支持注册服务到Nacos,以及支持Seata所有配置放到Nacos配置中心,在Nacos中统一维护;
高可用模式下就需要配合Nacos来完成
具体配置如下
注册中心
Seata-server端配置注册中心,在registry.conf中加入配置注册中心nacos
注意:确保client与server的注册处于同一个namespace和group,不然会找不到服务。
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"
}
......
配置中心
1.Seata-Server配置配置中心,在registry.conf中加入配置使用nacos作为配置中心
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
dataId = "seataServer.properties"
}
............
2.需要把Seata的一些配置上传到Nacos中,配置比较多,所以官方给我们提供了一个config.txt,我们下载并且修改其中参数,上传到Nacos中
下载地址:https://github.com/seata/seata/tree/develop/script/config-center
3.具体修改:
**注意:**事务分组:用于防护机房停电,来启用备用机房,或者异地机房,容错机制,当然如果Seata-Server配置了对应的事务分组,Client也需要配置相同的事务分组
service.vgroupMapping.可以自定义=default
default这里必须等于 registry.config 中的cluster="default"(当然可以更改 )
......
#-------------修改这个区域的映射--------------
transport.shutdown.wait=3
service.vgroupMapping.mygroup=default # 事务分组
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#------------------------------------------
......
store.mode=db # 修改
...
#-----------修改这个区域的JDBC连接-----------
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
4.修改好这个文件以后,需要把这个文件放到seata目录下
5.此时需要把这些配置一个个的加入到Nacos配置中,所以需要一个脚本来进行执行,官方已经提供好了,地址为:https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
6.在seata文件夹中新建一个脚本文件nacos-config.sh,然后把脚本内容复制进去
7.利用git来进行执行命令:
或者直接把nacos-config.sh拉入git的bash里执行
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 命名空间 -u nacos -w nacos
参数说明:
-h:host,默认值localhost
-p:port,默认值8848
-g:配置分组,默认为SEATA_GROUP
-t:租户信息,对应Nacos的命名空间ID,默认为空
8.在执行naocs-config文件的时候要注意,它默认寻找config.txt的路径和我们的路径不同,所以要打开naocs-config文件进行修改,否则无法执行。
测试启动
当以上的这些配置完成以后,我们就可以启动nacos和seata-server了,此时我们查看Nacos的配置中心,就会看到我们传入的所有配置信息
如果上述步骤过于繁琐可以通过dataId配置
1.从v1.4.2版本开始,已支持从一个Nacos datald中获取所有配置信息,你只需要额外添加一个datald配置项。
2.首先你需要在nacos新建配置,此处datald为seataServer.properties,配置内容参考https://github.com/seata/seata/tree/develop/script/config-center的config.txt并按需修改保存
如果是集群部署方式,如何修改端口?
在 Linux/Mac 下
$ sh ./bin/seata-server.sh
在 Windows 下
bin\seata-server.bat
支持的启动参数
参数 | 全写 | 作用 | 备注 |
---|---|---|---|
-h | –host | 指定在注册中心注册的 IP | 不指定时获取当前的 IP,外部访问部署在云环境和容器中的 server 建议指定 |
-p | –port | 指定 server 启动的端口 | 默认为 8091 |
-m | –store | Mode事务日志存储方式 | 支持file ,db ,redis ,默认为 file 注:redis需seata-server 1.3版本及以上 |
-n | –serverNode | 用于指定seata-server节点ID | 如 1 ,2 ,3 …, 默认为 1 |
-e | –seataEnv | 指定 seata-server 运行环境 | 如 dev , test 等, 服务启动时会使用 registry-dev.conf 这样的配置 |
$ sh ./bin/seata-server.sh -p 8091 -h 127.0.0.1 -m file
AT
AT模式是一种无侵入的分布式事务解决方案,在AT模式下,用户只需关注“业务SQL”,用户的“业务SQL”作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。(基于自定义的日志表
)
整体机制
两阶段提交协议的演变:
一阶段:业务数据和回滚日志在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
- 提交异步化,快速完成
- 回滚通过一阶段的回滚日志进行返向补偿。
一阶段
在一阶段中,Seata会拦截“业务SQL”,首先解析SQL语义,找到要更新的业务数据,在数据被更新前,保存下来“undo”,然后执行执行“业务SQL”更新数据,更新之后再次保存数据“redo”,最后生成行锁
,这些操作都是在本地事务中完成,这样保证了一阶段的原子性
(找到对应的RM,将数据添加进undo中,执行sql,保存进redo中,生成行锁)
二阶段
相当于一阶段,二阶段比较简单,负责整体的回滚和提交,如果一阶段中有本地事务没通过,那么就执行全局回滚,反之进行全局提交,回滚中用到的就是一阶段记录的undolog ,通过回滚记录生成反向更新sql并执行,以完成分支的回滚。导入事务完成之后会释放所有的资源并删除所有的日志
图解
案例
通过Seata的AT模式解决分布式事务
首先增加对应的Seata依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
在对应的微服务数据库上加上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;
配置yml
spring:
application:
name: seata-order
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
tx-service-group: mygroup # 事务组, 随便写
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test_at?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
seata:
tx-service-group: mygroup # 事务组名称,要和服务端对应
service:
vgroup-mapping:
mygroup: default # key是事务组名称 value要和服务端的机房名称保持一致
在™的Controller上添加注解
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/order/create")
@GlobalTransactional// 开启分布式事务
public String create(){
orderService.create();
return "生成订单";
}
}
Seata-XA模式
(XA是非补偿型的
,唯一一个保证全局数据的一致性
)
XA协议
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准
XA规范描述了全局的事务管理器与局部的资源管理器之间的接口。XA规范的目的是允许多个资源(如数据库,应用服务器,消息队列等)在同一个事务中访问,这样可以使得ACID属性跨越应用程序而保持有效。(XA完全基于数据库
,AT基于自定义的日志表)
XA规范使用两阶段提交(2PC)来保证所有资源同时提交或回滚任何特定的事务。
DTP模型定义如下角色:
- AP:即应用程序,可以理解为使用DTP分布式事务的程序(
调用了分布式事务的程序
) - RM:资源管理器,可以理解为事务的参与者,一般情况下指的是一个数据库的实例(MySQL),通过资源管理器对该数据库进行控制,资源管理器控制着分支事务
- TM:事务管理器,负责协调和管理事务,事务管理器控制着去阿奴事务,管理事务的生命周期,并协调着各个RM。全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这工作即是一个全局事务。
- DTP模式定义TM和RM直接通讯的接口规范叫XA,简单理解为数据库提供的2PC接口协议,基于数据库的XA协议来实现的2PC又称为XA方案。
解释:
1.应用程序(AP)持有订单库和商品库两个数据来源。
2.应用程序(AP)通过TM通知订单库(RM)和商品库(RM),来创建订单库和减库存,RM此时未提交事务,此时商品和订单资源锁定。
3.TM收到执行回复,只要有一方失败则分别向其他RM发送回滚事务,回滚完毕,资源锁释放。
4.TM收到执行回复,全部成功,此时向所有RM发起提交事务,提交完毕,资源锁释放。
XA协议存在的问题
如果一个参与全局事务的资源“失联”了(收不到分支事务结束的命令),那么它锁定的数据,将被一直锁定,进而产生死锁
。
Seata的事务模式
Seata定义了全局事务的框架。
全局事务 定义为若干 分支事务 的整体协调。
- TM向TC请求全局事务,包括发起(begin)、提交(commit)
- TM把代表全局事务的XID绑定到分支事务上
- RM向TC注册,把分支事务关联到XID代表的全局事务中(
把分支事务关联到全局事务中
) - RM把分支事务执行的结果上报到TC。(可选)
- TC发送分支指令给RM。提交(Branch Commit)或回滚(Branch Rollback)
seata的全局事务 处理过程,分为两阶段: - 执行阶段:执行分支事务,并保证执行结果是
可回滚的(Rollbackable)
和持久化的(Durable)
完成阶段:根据执行阶段 结果形成的决议,应用通过TM发出的全局提交或回滚请求给TC,TC命令驱动 分支事务 进行commit 或 rollback
Seata 的所谓事务模式是指:运行在 Seata 全局事务框架下的 分支事务 的行为模式。准确地讲,应该叫作 分支事务模式。
不同的 事务模式 区别在于 分支事务 使用不同的方式达到全局事务两个阶段的目标。
执行阶段如何保证执行结果是可回滚的
和持久化的
?
完成阶段收到TC的命令后,如何做到分支的提交和回滚?
AT模式的实现:基于自定义的日志表实现的(undo,redo)
执行阶段:
- 可回滚:根据SQL解析结果,记录回滚日志
- 持久化:回滚日志和业务SQL在同一个本地事务中提交到数据库
完成阶段:
- 分支提交:异步删除回滚日志记录
- 分支回滚:依据回滚任日炙进行返向补偿更新
XA模式的实现:完全基于数据库底层实现
在Seata定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对XA协议的支持,以XA协议的机制来管理分支事务的一种模式
执行阶段:
- 可回滚:业务SQL操作放在XA分支中进行,由资源对XA协议的支持来保证 可回滚
- 持久化:XA分支完成后,执行XA prepare,同样,由于资源对XA协议的支持来保证持久化(即,之后任何意外都不会造成无法回滚的情况)
完成阶段:
- 分支提交:执行 XA 分支的 commit
- 分支回滚:执行 XA 分支的 rollback
Seata与AT、TCC、SAGA区别?
AT、TCC、Saga都是补偿型的。
补偿型 事务处理机制建立在 事务资源 之上(要么在中间件层面,要么在应用层面),事务资源 本身对分布式事务是无感知的。
(补偿,分布式系统下短时间内数据的不一致,通过消息队列或其他方式使数据一致)
事务资源对分布式事务的无感知存在一个根本的问题:无法做到真正的 全局一致性。
XA的优势
与 补偿型 不同,XA 协议 要求 事务资源 本身提供对规范和协议的支持。(XA是非补偿型的
)
因为 事务资源 感知并参与分布式事务处理过程,所以 事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离, 满足全局数据一致性
。
XA的其他优势:
业务无侵入性:和 AT 一样,XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担。
数据库的支持广泛:XA协议被主流关系型数据库广泛支持,不需要额外的适配即可使用。
多语言支持容易:因为不涉及 SQL 解析,XA 模式对 Seata 的 RM 的要求比较少。
传统基于 XA 应用的迁移:传统的,基于 XA 协议的应用,迁移到 Seata 平台,使用 XA 模式将更平滑。
在当前的技术发展阶段,不存一个分布式事务处理机制可以完美满足所有场景的需求。
一致性、可靠性、易用性、性能等诸多方面的系统设计约束,需要用不同的事务处理机制去满足。
Seata 项目最核心的价值在于:构建一个全面解决分布式事务问题的 标准化 平台。
基于 Seata,上层应用架构可以根据实际场景的需求,灵活选择合适的分布式事务解决方案。
XA使用:更换数据源即可
TCC事务模式
TCC是分布式事务中的二阶段提交协议,全称为Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel)
Try:对俄乌资源的检查并预留;
Confirm:对业务处理进行提交,即commit操作,只要try成功,那么该步骤一定成功;
Cancel:对业务处理进行取消,即回滚操作,该步骤会对Try预留的资源进行释放。
TCC是一种侵入式的分布式事务解决方案,,以上三个操作都需要业务自行实现,对业务系统有着非常大的侵入性,设计相对复杂,但优点是TCC完全不依赖数据库,能实现数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现原子操作,更好地解决在各种复杂业务场景下的分布式事务问题。
Seata TCC 模式跟通用型 TCC 模式原理一致。
TCC和AT区别
AT模式基于本地ACID事务
的关系型数据库
:
- 一阶段prepare:在本地事务中,一并提交业务更新和相对应的回滚日志记录。
- 二阶段commit: 通过持久化日志,
自动
异步批量清理回滚日志 - 二阶段rollback:通过回滚日志,
自动
生成补偿操作,完成数据回滚。
相对应的,TCC模式,不依赖于底层数据资源的事务支持:
- 一阶段prepare行为:调用
自定义
的 prepare 逻辑。 - 二阶段 commit 行为:调用
自定义
的 commit 逻辑。 - 二阶段 rollback 行为:调用
自定义
的 rollback 逻辑。
所谓TCC模式,是指支持把自定义的分支事务纳入到全局事务的管理中。
特点:
确认信强,且需要自己实现相关事务控制逻辑
在整个过程中基本没有锁
,性能较强
具体使用案例:https://seata.io/zh-cn/blog/integrate-seata-tcc-mode-with-spring-cloud.html
Saga事务模式
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务(执行处理时候出错了,给一个修复的机会)都由业务开发实现。
Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。
Sgag的优势
在一些特殊环境下,比如老系统,封闭的系统(无法修改,同时没有任何分布式事务引入),那么AT、XA、TCC模型将全部不能使用,为了解决这样的问题,才引用了Saga模型。
比如:事务参与者可能是其他公司的服务或者是遗留系统,无法改造,可以使用Saga模式。
Saga模式是Seata提供的长事务解决方案,提供了异构系统的事务统一处理模型。在Saga模式中,所有的子业务都不在直接参与整体事务的处理(只负责本地事务的处理),而是全部交由了最终调用端来负责实现,而在进行总业务逻辑处理时,在某一个子业务出现问题时,则自动补偿全面已经成功的其他参与者,这样一阶段的正向服务调用和二阶段的服务补偿处理全部由总业务开发实现。
Saga状态机
Seata提供的Saga模式只能通过状态机引擎来实现,需要手动进行Saga业务流程绘制,并且将其转换为Json配置文件,而后在程序运行时,将依据子配置文件实现业务处理以及服务补偿处理,而要想进行Saga状态图的绘制,一般需要通过Saga状态机来实现。
基本原理:
- 通过状态图来定义服务调用的流程并生成json定义文件
- 状态图中一个节点可以调用一个服务,节点可以配置它的补偿节点
- 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚
- 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能
Saga状态机的应用
官方提供了一个状态机设计器
官方文档地址:https://seata.io/zh-cn/docs/user/saga.html
Seata Safa状态机可视化图形设计器使用地址:https://github.com/seata/seata/blob/develop/saga/seata-saga-statemachine-designer/README.zh-CN.md
Seata源码
入口
GlobalTransactionScanner 全局事务扫描器
实现了InitializingBean接口重写了afterPropertiesSet(),在Spring容器创建时,将TM,RM初始化并向TC注册
继承了AbstractAutoProxyCreator (AOP抽象类,生成代理对象)
继承此接口重写wrapIfNecessary方法,调用拦截器,执行对应的invoke()方法
核心是invoke方法
seata核心流程总结:
- 在Spring初始化时,将TM,RM进行初始化
- 解析GlobalTransactional注解并创建
事务代理对象
,并添加进拦截器globaltransactionalinterceptor中 - 获得当前事务
- 开始执行
全局
事务 - 执行当前的业务逻辑
- (1)在TC注册当前分支事务(RM)
,向Branch_table
中插入一条分支事务数据
- (2)执行本地update,并将前后数据生成镜像放入undo_log表
中
- (3)调用其他分支的操作,其他分支注册对应的分支事务
- (4)在lock_table表中插入全局锁
数据(一个分支一条sql) - 如果发送异常通过undolog进行回滚补偿
- 提交全局事务
- 清除所有资源
(全局锁的作用:多线程模式下防止出现脏写的情况)