1.下载相关文件与源码: https://github.com/seata/seata/releases(记得源码也要下载,后续需要)
2.创建数据库表: 去源码文件夹里找到mysql.sql,用数据库可视化工具创建好表
3.修改配置文件file.conf: 修改mysql连接的配置信息,这个文件在conf目录下(不是源码)
dbType = "mysql"
driverClassName = "com.mysql.cj.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&autoReconnect=true&serverTimezone=Asia/Shanghai&useSSL=false"
user = "root"
password = "123456"
4.前往nacos添加命名空间: 添加Seata命名空间,用来保存Seata相关的配置信息
5.修改配置文件register.conf: 将配置信息保存在nacos中,修改配置文件
6.将配置文件同步到nacos中: 前往源码找到config.txt文件,修改数据库连接信息,该文件在seata-1.4.2scriptconfig-center
7.将配置文件同步到nacos: 前往seata-1.4.2scriptconfig-centernacos下,打开git控制台,使用命令将信息上传到nacos里
#这里的命名空间ID,分组等信息要改成自己的 sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t 43fd994f-7ee4-47f4-bd16-28b39f603c6d -u nacos -w nacos
8.检查配置文件是否同步: 去nacos里看配置列表
9.启动Seata: 前往bin目录下,点击启动文件seata-server.bat
1.搭建相关测试项目: 本次测试主要搭建两个服务,一个模拟库存服务,一个模拟订单服务,库存服务主要是减少库存,而订单服务则是生成相对应的订单。主要模拟的是一个下单的操作,就是说:一个下单操作,应该是减少相对应的商品库存信息,另一个则是生成相对应的订单信息。
项目gitee地址: https://gitee.com/longjiamou/spring-cloud-alibaba.git
部分主要代码和配置:
pom依赖导入: 主要是导入seata,nacos,mybatis-plus等相关依赖
<!--要与服务端的版本一致 --> com.alibaba.cloud spring-cloud-starter-alibaba-seata seata-all io.seata io.seata seata-spring-boot-starter io.seata seata-all 1.4.2
相关seata配置编写:
seata:
# 是否开启seata,默认true
enabled: true
application-id: seata-server
# seata事务组的名称,一定要和config.tx(nacos)中配置的相同
tx-service-group: default
# 配置中心的配置
config:
# 使用类型nacos
type: nacos
# nacos作为配置中心的相关配置,需要和server在同一个注册中心下
nacos:
# 命名空间,需要server端(registry和config)、nacos配置client端(registry和config)保持一致
namespace: 43fd994f-7ee4-47f4-bd16-28b39f603c6d
# 地址
server-addr: localhost:8848
# 组, 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
group: SEATA_GROUP
# 用户名和密码
username: nacos
password: nacos
registry:
type: nacos
nacos:
# 这里的名字一定要和seata服务端中的名称相同,默认是seata-server
application: seata-server
# 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
group: SEATA_GROUP
namespace: 43fd994f-7ee4-47f4-bd16-28b39f603c6d
username: nacos
password: nacos
server-addr: localhost:8848
生成订单接口编写: server-order-8082下的orderServiceImpl
@Transactional
public String add(@RequestBody Orders book){
int insert = mapper.insert(book);
System.out.println(insert);
if(insert>0){
return "创建订单成功";
}else {
return "创建订单失败";
}
}
减少库存接口编写: 在这里使用了该方法作为seata的TM
@Transactional
@GlobalTransactional
public String BuyBook(Orders book){
//1.查询相关书籍库存信息
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("book_id",book.getBookId());
Book book1 = mapper.selectOne(wrapper);
//2.减少相关库存
int i = book1.getBookCount() - book.getOrderCounts();
book1.setBookCount(i);
int i1 = mapper.update(book1,wrapper);
//3.生成订单
String order = feigin.add(book);
System.out.println(order);
return order;
}
2.测试1: 测试没有加全局事务管理并且让程序能正常运行
@Transactional
// @GlobalTransactional
public String BuyBook(Orders book){
System.out.println("全局事务ID:"+RootContext.getXID());
//1.查询相关书籍库存信息
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("book_id",book.getBookId());
Book book1 = mapper.selectOne(wrapper);
//2.减少相关库存
int i = book1.getBookCount() - book.getOrderCounts();
book1.setBookCount(i);
int i1 = mapper.update(book1,wrapper);
//3.生成订单
String order = feigin.add(book);
System.out.println(order);
return order;
}
3.测试2: 测试没有加全局事务管理,当时服务出现了异常,比如读取超时等情况,这里让创建订单的响应时间为2秒,这时候就会出现超时情况,因为feigin的调用时间为1秒。
@Transactional
public String add(@RequestBody Orders book) throws InterruptedException {
Thread.sleep(2000);
int insert = mapper.insert(book);
System.out.println(insert);
if(insert>0){
return "创建订单成功";
}else {
return "创建订单失败";
}
}
3.测试3: 加了全局事务,并且让服务出错,看看事务是否回滚
@Transactional
@GlobalTransactional
public String BuyBook(Orders book){
System.out.println("全局事务ID:"+RootContext.getXID());
//1.查询相关书籍库存信息
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("book_id",book.getBookId());
Book book1 = mapper.selectOne(wrapper);
//2.减少相关库存
int i = book1.getBookCount() - book.getOrderCounts();
book1.setBookCount(i);
int i1 = mapper.update(book1,wrapper);
//3.生成订单
String order = feigin.add(book);
System.out.println(order);
return order;
}
//server-order-8082里的接口方法,模拟创建订单功能失败
@Transactional
public String add(@RequestBody Orders book){
int i = 1/0;
int insert = mapper.insert(book);
System.out.println(insert);
if(insert>0){
return "创建订单成功";
}else {
return "创建订单失败";
}
}
AT模式主要分为两个阶段,三张数据库表以及Seata中的三个角色(TC,TM,RM),每一个阶段都有不同的功能,每一张表都记录事务执行过程中所涉及到的一些数据,如:全局事务ID,子事务ID等等。
所涉及到的三张表如下:
global_table: 全局事务表,每当有一个全局事务发起后,就会在该表中记录全局事务的ID
branch_table: 分支事务表,记录每一个分支事务的ID,分支事务操作的哪个数据库等信息
lock_table: 全局锁
一阶段步骤:
- TM --> service-provider-8081:BuyBook()方法执行时,由于该方法具有@GlobalTranscational标志,该TM会向TC发起全局事务,生成XID(全局锁)
- RM --> service-provider-8081:BuyBook():写表,UNDO_LOG记录回滚日志(Branch ID),通知TC操作结果
- RM --> service-order-8082:add():写表,UNDO_LOG记录回滚日志(Branch ID),通知TC操作结果
简单理解: RM,即我们的本地事务,在写表的过程,Seata 会拦截业务SQL ,首先解析 SQL 语义,在业务数据被更新前,将其保存成before image (前置镜像),然后执行业务SQL ,在业务数据更新之后,再将其保存成after image (后置镜像),最后生成行锁。以上操作全部在一个数据库事务内完成(在seata数据库),这样保证了一阶段操作的原子性。
二阶段步骤(提交):
因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。TM执行成功之后,通知TC全局提交,TC此时通知所有的RM提交成功,删除UNDO_LOG回滚日志。
二阶段步骤(失败回滚):
TM执行失败,通知TC全局回滚,TC此时通知所有的RM进行回滚,根据UNDO_LOG反向操作,使用before image 还原业务数据,删除UNDO_LOG,但在还原前要首先要校验脏写,**对比“数据库当前业务数据”和 “after image”**来校验数据是否出现脏读现象,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。



