一、seata介绍
1.seata安装
seata 安装的安装,可以参考另外一篇博客
https://blog.csdn.net/zxd1435513775/article/details/121871487
2.seata简介
seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
seata 官方网址:https://seata.io/zh-cn/
3. 分布式架构
在分布式之前,单机单库或者,多个微服务连接同一个数据库,事务的一致性可以得到解决,使用@Transactional 注解就好,此注解的事务是面对的同一个数据源。
但在分布式之后,单体应用被拆分成多个微服务应用,每个微服务应用都连接各自独立的数据库,分别使用自己独立的数据源,此时每个微服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
4.seata的过程ID和三组件模型
Transaction ID XID :全局唯一的事务ID
三组件模型
Transaction Coordinator(TC) : 事务协调者,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚Transaction Manager(TM) : 事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议Resource Manager(RM) :资源管理器,控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
5. seata处理事务过程
-
TM 向TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID -
XID 在微服务调用链路的上下文中传播 -
RM 向TC 注册分支事务,将其纳入XID 对应全局事务的管辖 -
TM 向TC 发起针对XID 的全局提交或回滚决议 -
TC 调度XID 下管辖的全部分支事务完成提交或回滚请求
二、seata集成示例
1.案例分析
模拟场景,创建订单、修改库存数量、扣减用户余额,分别对应三个微服务:
seata-order-service 订单服务seata-storage-service 库存服务seata-account-service 账户服务
三个服务的调用顺序如下:
订单服务创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->订单服务修改订单状态
文章末尾提供示例源码,包括mysql初始化脚本,但数据库得自己创建
分别创建三个数据库,与三个服务对应:seata-order、seata-storage、seata-account
创建完三个数据库后,要执行seata 的脚本:
github地址:https://github.com/seata/seata/blob/develop/script/client/at/db/mysql.sql
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
另外要把另外三个库的初始化脚本也执行完,最后数据库结构如下:
三个数据库数据的初始状态如下:
订单库没有订单
库存库有100件库存
账户库余额有1000
如果三个服务之间,没有发生异常,程序也没有集成seata ,程序正常调用,在浏览器中输入如下地址,用get 方式容易测试。正常情况下,没有问题。
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
订单创建成功,支付状态修改成功
库存扣减成功
账户余额扣减成功
现在,要模拟程序发送网络异常或者程序异常的情况,在seata-account-service 中,让线程阻塞,同时设置openFeign 调用超时时间为5秒
# 设置openFeign超时时间
feign.client.config.default.connect-timeout=5000
feign.client.config.default.read-timeout=5000
public void decrease(Long userId, BigDecimal money) {
log.info("------->account-service中扣减账户余额开始");
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
accountDao.decrease(userId,money);
log.info("------->account-service中扣减账户余额结束");
}
然后,再次访问刚才的地址:
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
会发现,seata-order-service 服务报了超时异常,但订单已经创建成功,并且库存量和账户余额都被扣减,但订单的状态仍然是未支付状态,这样是不对的!!!!
订单创建成功,但支付状态为:未支付。0表示未支付,1表示已支付。
库存扣减成功
账户余额扣减成功,但订单的状态仍为:未支付!!!
2.seata管理分布式事务
下面就需要用seata 来管理分布式事务。在做下面事情之前,要启动Nacos 和Seata 服务!!!
1.引依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2021.1</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
2.改配置文件
每个微服务的配置文件都要加seata 配置!!!
# seata配置
seata.enabled=true
seata.enable-auto-data-source-proxy=true
# config.txt文件中修改的vgroupMapping值
seata.tx-service-group=scorpios_tx_group
seata.registry.type=nacos
# registry.conf配置文件中 nacos的application名称
seata.registry.nacos.application=seata-server
seata.registry.nacos.server-addr=localhost:8848
seata.registry.nacos.username=nacos
seata.registry.nacos.password=nacos
seata.registry.nacos.namespace=88b8f583-43f9-4272-bd46-78a9f89c56e8
seata.registry.nacos.cluster=default
seata.registry.nacos.group=SEATA_GROUP
seata.config.type=nacos
seata.config.nacos.server-addr=localhost:8848
seata.config.nacos.namespace=88b8f583-43f9-4272-bd46-78a9f89c56e8
seata.config.nacos.group=SEATA_GROUP
seata.config.nacos.username=nacos
seata.config.nacos.password=nacos
3.开启注解
每个微服务都要开启seata !!!
@SpringBootApplication
@MapperScan({"com.scorpios.order.dao"})
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.scorpios")
@EnableAutoDataSourceProxy
public class SeataOrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SeataOrderServiceApplication.class, args);
}
}
4.使用注解
在seata-order-service 的业务方法上,添加seata 分布式事务注解:@GlobalTransactional
@GlobalTransactional(name = "scorpios-create-order",rollbackFor = Exception.class)
@Override
public void create(Order order){
log.info("----->开始新建订单");
orderDao.create(order);
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了");
}
5.测试结果
再次输入如下地址:
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
会发现seata-order-service 会报超时异常,同时seata 已经完成数据回滚
再去观察三个数据库,发现数据并没有任何变化,此处就不贴图了,seata 集成完毕。
一句话总结,就是一个注解@GlobalTransactional 的使用,哈哈哈~
项目源码地址:https://github.com/Hofanking/springboot-seata-example
|