🔰 学习视频 🔰
尚硅谷SpringCloud框架开发教程(SpringCloudAlibaba微服务分布式架构丨Spring Cloud)
🔰 项目地址 🔰
https://gitee.com/zqcliudaliuda/cloud2021
一、分布式问题
1对1:单机模式、一台服务器上有服务,另一台服务器上有数据库,两个相对应。
1对N:一台服务器上有服务,多台服务器上有数据库,一个调用多个。
N对N:N台服务器上有不同的服务,每个服务对应自己的一个数据库,如下面这个例子。
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
仓储服务:对给定的商品扣除仓储数量。
订单服务:根据采购需求创建订单。
帐户服务:从用户帐户中扣除余额。
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证, 但是全局的数据一致性问题没法保证。
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
二、Seata简介
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
2.1 术语
🔶 一ID和三组件模型
- XID(Transaction ID) - 全局事务ID
- TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。 - TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。 - RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
🔶 处理过程
- TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;
- XID在微服务调用链路的上下文中传播; .
- RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;
- TM向TC发起针对XID的全局提交或回滚决议;
- TC调度XID下管辖的全部分支事务完成提交或回滚请求。
2.2 注解
只需要使用一个@GlobalTransactional
注解在业务方法上:
三、安装
3.1 下载
下载:https://github.com/seata/seata/releases
下载完成后解压。
3.2 file.conf配置
先备份conf/file.conf
再修改。
主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接
自定义事务组名称
持久化方法改成数据库
配置数据库
3.3 数据库
创建数据库
使用seata-server-0.9.0\seata\conf\db_store.sql
创建数据库表格。
创建完成后如下:
3.4 registry.conf配置
修改注册中心为nacos,且配置注册地址。
3.5 启动
运行seata-server-0.9.0\seata\bin\seata-server.bat
在nacos可查看到:
四、分布式业务测试
以下演示要先启动Nacos后再启动Seata。
这里我们会创建三个服务,一个订单服务, 一个库存服务, 一个账户服务。当用户下单时,会在订单服务中创建一个订单, 然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
分布式事务业务说明:下订单——扣库存——减账户余额
4.1 数据库准备
4.1.1 数据库建立
seata_ order:存储订单的数据库;
seata_ storage:存储库存的数据库;
seata_ account:存储账户信息的数据库。
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;
4.1.2 业务表
seata_order库下建t_order表
USE seata_order;
CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`count` INT(11) DEFAULT NULL COMMENT '数量',
`money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
`status` INT(1) DEFAULT NULL COMMENT '订单状态:0创建中,1已完结'
)ENGINE=INNODB AUTO_INCREMENT=7 CHARSET=utf8;
seata_storage库下建t_storage 表
USE seata_storage;
CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`total` INT(11) DEFAULT NULL COMMENT '总库存',
`used` INT(11) DEFAULT NULL COMMENT '已用库存',
`residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
)ENGINE=INNODB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100);
seata_account库下建t_account表
USE seata_account;
CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用额度',
`residue` DECIMAL(10,0) DEFAULT 0 COMMENT '剩余可用额度'
)ENGINE=INNODB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);
4.1.3 回滚日志表
找到\seata-server-0.9.0\seata\conf\db_undo_log.sql
在3个库当中分别运行。
4.2 微服务准备
可在Gitee内查看详细代码。
业务需求:下订单——减库存——扣余额——改订单状态
🔶 新建maven项目seata-order-service2001
🔶 POM依赖
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>seata-all</groupId>
<artifactId>io.seata</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<!-- SpringCloud alibaba sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringCloud alibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud alibaba sentinel-datasource-nacos 持久化用的-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
🔶 application.yaml
配置
sserver:
port: 2001
spring:
application:
name: seata-order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
# 自定义事务组名称需要与seata-server中的配置相对应。
tx-service-group: my_test_tx_group
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order
username: root
password: 123456
🔶 file.conf
配置
复制之前配置好的file.conf
到项目的resources目录下。
🔶 registry.conf
配置
同样,复制之前配置好的registry.conf
到项目的resources目录下。
🔶 其他
编写一些业务类、配置等。
4.3 测试
查看数据库中的当前的状态
SELECT * FROM `seata_order`.`t_order`;
SELECT * FROM `seata_storage`.`t_storage`;
SELECT * FROM `seata_account`.`t_account`;
🔶 正常下单
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
{"code":200,"message":"订单创建成功","data":null}
未出任何问题的时候,三个数据库的数据都正常变化,该减少的减少,该增加的增加。
🔶 超时异常,没添加@GlobalTransactional
让账户业务超时20秒,看看效果
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100, 报错
当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1,而且由于feign的重试机制,账户余额还有可能被多次扣减。
🔶 超时异常,添加@GlobalTransactional
在order业务入口添加@GlobalTransactional
注解,当产生异常时进行回滚,保证数据的正确。