学习视频
尚硅谷SpringCloud框架开发教程(SpringCloudAlibaba微服务分布式架构丨Spring Cloud)
项目地址
https://gitee.com/zqcliudaliuda/cloud2021
1对1:单机模式、一台服务器上有服务,另一台服务器上有数据库,两个相对应。
1对N:一台服务器上有服务,多台服务器上有数据库,一个调用多个。
N对N:N台服务器上有不同的服务,每个服务对应自己的一个数据库,如下面这个例子。
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
仓储服务:对给定的商品扣除仓储数量。
订单服务:根据采购需求创建订单。
帐户服务:从用户帐户中扣除余额。
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证, 但是全局的数据一致性问题没法保证。
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
中文官网:https://seata.io/zh-cn/
一ID和三组件模型
处理过程
只需要使用一个@GlobalTransactional
注解在业务方法上:
下载:https://github.com/seata/seata/releases
下载完成后解压。
先备份conf/file.conf
再修改。
主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接
创建数据库
使用seata-server-0.9.0\seata\conf\db_store.sql
创建数据库表格。
运行seata-server-0.9.0\seata\bin\seata-server.bat
以下演示要先启动Nacos后再启动Seata。
这里我们会创建三个服务,一个订单服务, 一个库存服务, 一个账户服务。当用户下单时,会在订单服务中创建一个订单, 然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
分布式事务业务说明:下订单——扣库存——减账户余额
seata_ order:存储订单的数据库;
seata_ storage:存储库存的数据库;
seata_ account:存储账户信息的数据库。
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;
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);
找到\seata-server-0.9.0\seata\conf\db_undo_log.sql
在3个库当中分别运行。
可在Gitee内查看详细代码。
业务需求:下订单——减库存——扣余额——改订单状态
新建maven项目seata-order-service2001
POM依赖
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
<exclusions>
<exclusion>
<groupId>seata-allgroupId>
<artifactId>io.seataartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-allartifactId>
<version>0.9.0version>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
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目录下。
其他
编写一些业务类、配置等。
查看数据库中的当前的状态
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
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100, 报错
当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1,而且由于feign的重试机制,账户余额还有可能被多次扣减。
超时异常,添加@GlobalTransactional