Seata分布式事务
1.事务简介
在数据库系统里而言,事务是代表一个或者一系列操作的最小逻辑单元,所有在这个逻辑单元内的操作要么全部成功,要么就全部失败,不存在任何中间状态,一旦事务失败那么所有的更改都会被撤销,一旦事务成功所有的操作结果都会被保存。
1.1 事务的特性(ACID)
1.1.1 原子性(Atomicity)
概念:一个事务必须是一系列操作的最小单元,这系列操作的过程中,要么整个执行,要么整个回滚,不存在只执行了其中某一个或者某几个步骤。
例如转账操作,原子性就代表(检查余额、转账、到账)三个步骤就是一个整体,少了任何一个都不能称为一次转账,整个过程中检查余额、转账、到账要么整体都执行,要么一个失败就整体失败,绝对不会出现某一个执行成功其他的都执行失败,或者某一个执行失败其他的操作执行成功的情况。
1.1.2 一致性(Consistency)
概念:事务要保证数据库整体数据的完整性和业务数据的一致性,事务成功提交整体数据修改,事务错误则回滚到数据回到原来的状态。
例如转账操作,如果事务提交成功则A账户减金额,B账户则加对应的金额,数据库总体金额不变只是载体变了。如果事务出错则整体回滚,无论到了上面的哪个步骤A和B的数据都会回到事务开启前的状态保证数据的始终一致。
1.1.3 隔离性(Isolation)
概念:隔离性是说两个事务的执行都是独立隔离开来的,事务之前不会相互影响,多个事务操作一个对象时会以串行等待的方式保证事务相互之间是隔离的。
1.1.4 持久性(Durability)
概念:持久性是指一旦事务成功提交后,只要修改的数据都会进行持久化(通常是指数据成功保存到磁盘),不会因为异常、宕机而造成数据错误或丢失。
1.2 本地事务
大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所示:
1.3 分布式事务
以用户购买商品的业务逻辑为案例,整个业务逻辑由3个微服务提供支持:
- 仓储服务:对给定的商品扣除仓储数量。
- 订单服务:根据采购需求创建订单。
- 帐户服务:从用户帐户中扣除余额。
2.Seata 是什么?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
2.1 Seata的三大角色
2.1.1 TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
2.1.2 TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局 事务、提交或回滚全局事务。
2.1.3 RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。
2.2 Seata的生命周期
在 Seata 中,一个分布式事务的生命周期如下:
1.TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起(当一进入事务方法中就会生成XID , 将全局事务信息存储在global_table表中)。
2.RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联(当运行数据库操作方法,事务参与者会存储在branch_table表里)。
3.TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
4.TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。
2.3 Seata AT模式
Seata支持多种分布式事务模式,官方主推的是AT模式,AT模式是一种无侵入式的、类似于自动化的隐式TCC模式,是一种改进后的两阶段提交。
2.3.1 两阶段提交协议
一阶段:业务数据和undolog记录在同一个事务中并提交,随后释放本地锁和连接资源。
二阶段:
分布式事务操作成功,则异步提交。
分布式事务操作失败,则根据undolog表记录的数据进行反向补偿。
2.3.2 AT模式的优点
1.自动化的代理了DataSource,自动化实现了补偿逻辑,降低了业务代码入侵。
2.TC事务协调者的引入,负责事务的注册、回滚,从而简化了RM事务控制的逻辑。
3.分布式事务的隔离性通过全局锁和本地锁来实现的读隔离和写隔离。
2.3.3 AT模式的缺点
性能损耗
一条Update的SQL,则需要全局事务xid获取(与TC通讯)、before image(解析SQL,查询一次数据库)、after image(查询一次数据库)、insert undo log(写一次数据库)、before commit(与TC通讯,判断锁冲突),这些操作都需要一次远程RPC通讯,而且是同步的。另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间。
性价比
为了进行自动补偿,需要对所有交易生成前后镜像并持久化,可是在实际业务场景下,这个是成功率有多高,或者说分布式事务失败需要回滚的有多少比率?按照二八原则预估,为了20%的交易回滚,需要将80%的成功交易的响应时间增加5倍,这样的代价相比于让应用开发一个补偿交易是否是值得?
全局锁
①热点数据
相比XA,Seata 虽然在一阶段成功后会释放数据库锁,但一阶段在commit前全局锁的判定也拉长了对数据锁的占有时间,这个开销比XA的prepare低多少需要根据实际业务场景进行测试。全局锁的引入实现了隔离性,但带来的问题就是阻塞,降低并发性,尤其是热点数据,这个问题会更加严重。
②回滚锁释放时间
Seata在回滚时,需要先删除各节点的undo log,然后才能释放TC内存中的锁,所以如果第二阶段是回滚,释放锁的时间会更长。
③死锁问题
Seata的引入全局锁会额外增加死锁的风险,但如果出现死锁,会不断进行重试,最后靠等待全局锁超时,这种方式并不优雅,也延长了对数据库锁的占有时间。
3.Seata 快速开始
3.1 Seata Server(TC)环境搭建
3.1.1 组件版本关系
3.1.2.下载安装包
3.1.3 配置文件详解
① 在 ${SEATA_HOME}/conf 目录下有两个配置文件,分别是registry.conf和file.conf
registry.conf
registry.conf包含两项配置:
registry{}
:配置Seata Server的注册中心。config{}
:配置Seata Server的配置中心。file.conf
用于指定 Seata Server存放日志的位置。
② 在seata源码里的 /script/ 目录下有三个文件夹:
- /client:存放client端sql脚本,参数配置。
- /config-center:各个配置中心参数导入脚本,config.txt(包含server和client配置)为通用参数文件。
- /server:server端数据库脚本及各个容器配置。
3.1.4 修改配置文件
① registry.conf
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = ""
password = ""
}
}
config {
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
}
}
② file.conf
store {
mode = "db"
db {
datasource = "druid"
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://120.92.21.208:16371/tikin_seata"
user = "root"
password = "YRxAZw7BPSskHWiX"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
把seata源码里的 /script/server/db/mysql.sql 表文件导入到参数配置的tikin_seata库里。
备注:
Seata Server端存储模式(store.mode)支持三种:
- file:(默认)单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)。
- db:(5.7+)高可用模式,全局事务会话信息通过db共享,相应性能差些。
- redis:Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险。
③ config.txt
修改seata源码里的 /script/config-center/config.txt配置
将config.txt配置到nacos服务
启动seata源码里的 /script/config-center/nacos/nacos-config.sh
sh 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 的namespace字段, 默认为空
启动成功后,会在nacos里看见seata的配置
备注:
事务分组与高可用配置:service.vgroupMapping.这个配置的‘my_test_tx_group’参数是可以自定义的,需要和客户端配置文件里的配置一致。
3.1.5 启动Seata Server
打开 ${SEATA_HOME}/bin 目录,里面有(windows和linux)启动脚本。
① windows启动:双击seata-server.bat脚本即可。
② linux集群部署:
sh bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test
sh bin/seata-server.sh -h 127.0.0.1 -p 8092 -m db -n 2 -e test
sh bin/seata-server.sh -h 127.0.0.1 -p 8093 -m db -n 3 -e test
启动成功后,会在nacos里看见Seata Server的服务
3.2 Seata Client快速开始
3.2.1 各服务配置seata client的pom依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
3.2.2 各微服务对应的数据库中添加undo_log表
表文件在seata源码里的 /script/client/at/db/mysql.sql
3.2.3 添加application.yml配置
seata:
tx-service-group: my_test_tx_group
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
application: seata-server
group: SEATA_GROUP
username:
password:
config:
type: nacos
nacos:
serverAddr: 127.0.0.1:8848
group: SEATA_GROUP
username:
password:
3.2.4 接口上添加@GlobalTransactional注解
3.2.5 版本bug
LocalDateTime转换异常