分布式事务解决方案——Seata
分布式事务解决方案——Seata
Seata是什么?
Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata三大角色
在Seata架构中,共涉及到三个角色:
- TC(Transaction Conrdinator)-事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM(Transaction Manager)- 事务管理器
定义全局事务的范围:开始全局事务,提交或回滚全局事务
- RM(Resource Manager)- 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中TC为单独部署的Server服务端,TM和RM为嵌入到应用的Client客户端。
AT模式
前提
- 基于本地ACID事务的关系型数据库
- Java应用,通过JDBC访问数据库
AT模式是一种无侵入的分布式事务解决方案,该模式下,用户只需关注自己的业务SQL,用户的业务SQL作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。
-
一阶段:
- Seata会连接业务代码,解析SQL语义。
- 执行业务代码要更新的业务数据,在业务数据被更新前,将其保存为“before image”
- 执行业务SQL,更新业务数据
- 查询更新后的数据,将其保存成"after image"
- 生成行锁
以上操作全部在一个数据库事务内完成,保证了一阶段操作的原子性
-
二阶段(提交)
- 因为业务SQL在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删除,完成数据清理即可。
-
三阶段(回滚)
-
校验脏写
- 首先要校验脏读,对比“数据库当前业务数据”和“after image”。
- 如果两份数据完全一致就说明没有脏读,可以还原业务数据。
- 如果不一致,说明有脏写,出现脏写就需要转人工处理。
-
还原数据
- 用"before image"还原业务数据
-
删除快照数据和行锁
-
设计亮点
相比较其他分布式事务框架,Seata架构的亮点如下:
- 应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性。
- 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚。
- 通过全局锁实现了写隔离和读隔离。
Seata 快速开始
Seata 安装包下载
注意:选择的Seata版本最好与Spring Cloud版本匹配对应,版本说明文档地址
![image-20230824102250798](https://img2023.cnblogs.com/blog/1483664/202308/1483664-20230824102256154-55564117.png)
本文使用Mac环境下seata-server1.3.0进行部署说明
https://github.com/seata/seata/releases/tag/v1.3.0
![image-20230824102726876](/Users/zhangshao/Library/Application Support/typora-user-images/image-20230824102726876.png)
事务信息存储配置
server端存储模式(store.model)支持如下三种方式:
- File
单机模式(默认为此模式),全局事务会话信息存储在内存中,读写并持久化至本地文件root.data(bin/sessionStore/root.data)中,性能较高
~/java_tools/seata/bin/sessionStore/root.data
- db
高可用模式(MySQL5.7+),全局事务会话信息通过db共享,性能较差一些。
- redis
Seata-server 1.3及以上版本支持,性能较高,但存在事务信息丢失风险,需配合redis持久化配置策略。
本次使用File单机模式进行启动配置,db模式在补充部分介绍。
进入bin目录,执行./seata-server.sh
Seata Client代码实现
业务场景:
用户下单,整个业务逻辑由两个服务系统组成。
订单服务:根据采购需要创建订单
库存系统:对给定的商品扣减库存数量。
配置微服务整合Seata
- 添加pom依赖
<!-- 添加 seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 在各服务对应的数据库中添加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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 添加事务分组
- 配置seata的注册中心
具体 yaml配置如下
server:
port: 8086
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_product
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root123456
cloud:
alibaba:
seata:
# 这里需要与seata.service.vGroup-mapping保持一致,
tx-service-group: seata-product
application:
name: seata-product
seata:
service:
vgroup-mapping:
seata-product: default
grouplist:
default: 127.0.0.1:8091
在方法上添加@GlobalTranscational即可。
@GlobalTransactional
public Boolean create(Integer count){
String url = "http://localhost:8086/deduct?productId=10001&count="+count;
Boolean result = restTemplate.getForObject(url, Boolean.class);
if(result!=null && result){
if(5 == count){
throw new RuntimeException("order 发生异常");
}
log.info("数据库开始创建订单");
return true;
}
return false;
}
补充
Seata Server端信息配置db模式
1. 打开`conf/file.conf`文件
2. 修改mode = 'db'
3. 修改数据库连接信息
4. 新建数据库seata及表结构:可以去seata提供的资源信息中下载https://github.com/seata/seata/tree/1.3.0,其sql位置在/script/server/db/mysql.sql;
配置Nacos注册中心,负责与TC通信
将Seata Server注册到nacos,修改config目录下的registry.conf配置
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
配置Nacos作为配置中心
- Seata-Server端配置nacos配置中心,在
registry.conf
中加入配置使用nacos作为配置中心
config {
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
其中的含义为:
- Registry:服务器要注册到nacos的位置(命名空间,集群地址)
- Config:Seata服务器配置,这里需要去nacos配置中心上去获取配置,所以需要推上去。
在nacos 主目录,创建config.txt
,内容拷贝git上的配置https://github.com/seata/seata/blob/1.3.0/script/config-center/config.txt
主要修改的内容如下
# 其中my_test_tx_group是可以自定义的且之后要与seata客户端中相匹配
service.vgroupMapping.my_test_tx_group=default
# 修改为db,下面修改到对应数据库的信息
store.mode=file
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=username
store.db.password=password
同在nacos主目录,创建nacos-config.sh,内容拷贝git上的配置https://github.com/seata/seata/blob/1.3.0/script/config-center/nacos/nacos-config.sh
主要修改的内容如下
# 需要修改nacos-config.sh文件第97行代码,用以获取config.txt文件
修改nacos-config的权限
chmod 777 ./nacos-config.sh
整个目录解构如下
运行nacos-config.sh
./nacos-config.sh -h 127.0.0.1 -p 8848 -u nacos -w nacos -g SEATA_GROUP
参数说明
-h host 默认值 localhsot
-p post 默认值 8848
-u nacos用户名
-w nacos密码
-g 配置分组 默认值为 ‘SEATA_GROUP’
-t 租户信息 对应Nacos的命名空间id字段,默认为空
随后登录nacos-server查看配置信息