携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情
一、创建项目基本模块
1-1、创建基于Spring Cloud Alibaba服务的seata服务
根据之前创建的Spring Cloud Alibaba服务再次创建一个子父级模块,并且添加相关依赖模块。
1-1-1、父项目pom依赖
Spring Cloud Alibaba父项目pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules>
<module>Order</module>
<module>stock</module>
<module>order-ribbon</module>
<module>order-loadbalancer</module>
<module>order-openfeign</module>
<module>nacos-config</module>
<module>order-sentinel</module>
<module>order-openfeign-sentinel</module>
<module>seata</module>
</modules>
<groupId>com.jony</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloudalibaba</name>
<description>springcloudalibaba</description>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
<spring-cloud-version>Hoxton.SR12</spring-cloud-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--spring cloud alibaba 的版本管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring boot 的版本管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud的版本管理-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
复制代码
1-1-2、seata项目父级项目pom依赖
创建依赖于上面父项目的seata子服务
seata项目创建pom.xml内容如下(如要添加了jdbc、web、mybatis、mysql驱动、druid连接池,并且添加了mybatis代码生成器):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloudalibaba</artifactId>
<groupId>com.jony</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Mybatis-Generator插件,自动生成代码 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<!--必須要引入数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--必须制定版本-->
<version>8.0.22</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
复制代码
1-2、创建订单服务
订单服务主要完成对订单的增删改查操作。
1-2-1、代码目录结构
完成对Mapper以及service和Controller的创建
1-2-1-1、Controller方法
接口方法主要负责对订单的保存
1-2-2、application.yml配置信息
# 数据源
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.253.131:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#初始化时运行sql脚本
schema: classpath:sql/schema.sql
initialization-mode: never
cloud:
nacos:
discovery:
server-addr: 192.168.253.131:8851
username: nacos
password: nacos
alibaba:
seata:
tx-service-group: beijing
application:
name: seata-order
#设置mybatis
mybatis:
mapper-locations: classpath:com/jony/order/mapper/*Mapper.xml
#config-location: classpath:mybatis-config.xml
typeAliasesPackage: com.jony.order
configuration:
mapUnderscoreToCamelCase: true
server:
port: 8070
复制代码
1-3、创建库存服务
1-3-1、代码目录结构
库存服务主要实现对库存的扣减
1-3-1-1、Controller方法
通过接收商品ID对库存进行扣减
1-3-1-2、Mapper方法
通过ID对库存进行数量-1操作
1-3-2、application.yml配置信息
主要对数据、mybatis、nacos进行相关配置
# 数据源
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.253.131:3306/seata_stock?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#初始化时运行sql脚本
schema: classpath:sql/schema.sql
initialization-mode: never
cloud:
nacos:
discovery:
server-addr: 192.168.253.131:8851
username: nacos
password: nacos
alibaba:
seata:
tx-service-group: beijing
application:
name: seata-stock
#设置mybatis
mybatis:
mapper-locations: classpath:com/jony/stock/mapper/*Mapper.xml
#config-location: classpath:mybatis-config.xml
typeAliasesPackage: com.jony.stock
configuration:
mapUnderscoreToCamelCase: true
server:
port: 8071
复制代码
1-4、数据库表
对订单和库存服务分别创建各自的数据库表,同时创建各自服务的undo_log信息。
通过以上操作就完成了订单服务和扣减库存服务的创建,下面看一下通过分布式服务如何进行服务的调用以及使用seata实现分布式事务的处理
二、微服务的调用及分布式事务
上面代码已经完成了各自服务代码功能的创建,但是还未完成服务之间的调用,之前的文章有提到,服务之间调用可以通过Nacos+Feign来处理,下面首先来进行这一步处理,然后再进行微服务分布式事务的处理
2-1、微服务Nacos+Feign的处理
2-1-1、添加nacos、Feign的依赖
<!--nacos 服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--openFeign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
复制代码
2-1-2、Nacos注册中心相关配置
分别给订单服务和库存服务配置nacos注册中心,同时修改服务名称
订单服务:seata-order
库存服务:seata-stock
application:
name: seata-order
#nacos注册中心的配置
cloud:
nacos:
discovery:
server-addr: 192.168.253.131:8851
username: nacos
password: nacos
复制代码
2-1-3、在服务调用端创建OpenFeign的接口
当前项目主要是订单调用库存服务,因此需要在订单服务中创建库存服务OpenFeign的调用接口
2-1-3-1、首先在启动类上添加@EnableFeignClients
通过添加@EnableFeignClients
,则代表启用feign客户端;扫描和注册feign客户端bean定义
2-1-3-2、创建stockService的OpenFeign接口
2-1-3-3、在订单服务service中通过OpenFeign调用库存服务
通过以下操作就可以完成、通过OpenFeign调用库存服务了
2-2、启动订单和库存服务
将订单和库存服务启动,在Nacos控制台就可以看到两个服务了
2-3、调用订单服务,通过OpenFeign调用库存服务
2-3-1、访问服务前数据库信息
订单服务目前数据库信息
库存服务数据数据信息
启动订单服务和库存服务,然后访问订单接口
2-3-2、访问服务后数据库信息
订单服务数据库信息
库存服务信息
2-3-2、小结
通过以上可以看到:
访问服务前:订单一条数据,库存99
访问服务后:订单二条数据,库存96\
这是因为我本地测试的时候,因为Nacos服务问题导致订单服务异常、库存服务正常。这样订单没成功、然后库存却扣减成功了。这样在分布式服务的场景下肯定是不行的。
三、配置微服务整合Seata
3-1、添加依赖
给分布式事务相关的微服务添加seata依赖
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
复制代码
3-2、给微服务添加undo_log数据库表
在前面的文章中已经讲述过使用seata实现分布式事务的原理,实际就是在事务执行前记录元数据和事务执行后也记录元数据
如果事务需要回滚则利用存储的元数据进行存储,
可以看下之前的文章juejin.cn/editor/draf…
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 DEFAULT CHARSET=utf8;
复制代码
3-3、微服务seata相关配置(订单和库存服务都需要配置)
3-3-1、seata分组配置
3-3-1-1、seata分组的设置
在前面的文章中有提到给seata进行分组,防止区域灾备处理。之前配置的文件在/seata/script/config-center
中的config.txt中如下:
在之前的文章中已经配置为beijing,如下:
3-3-1-2、微服务application.yml中seata分组配置
3-3-2、微服务中seata服务的注册中心配置
微服务要使用seata,还需要告诉微服务去哪里进行访问seata服务端
在spring cloud alibaba 2.1之前,需要将官方提供的register.conf放到微服务中的resource中。2.1之后在application.yml中进行配置即可(默认值未修改的可以不进行配置)。如下:
3-3-3、微服务中seata服务的配置中心配置
3-4、给微服务调用方法添加@GlobalTransactional
测试分布式事务
通过以上配置就完成了seata+nacos+feign分布式微服务的搭建,然后分别启动这两个微服务
启动的时候报了一个错误:如下:
no available service 'null' found, please make sure registry config correct
经过查找终于发现,是因为我把默认的public命名空间改为了seata。但是在配置文件中的namespace不能写seata,必须写seata的dataID.
如下(需要同时将订单和库存服务都修改了):
修改之后,再次启动就没有问题了。
3-4-1、让两个服务都成功请求
访问前:订单3条,库存96
访问后:订单4条,库存95,访问后数据库数据如下:
订单数据库:
库存数据库:
3-4-2、手动创建异常,查看分布式事务是否可以生效
通过添加执行异常代码,查看事务是否回滚
访问创建订单接口,可以看到已经发生异常。
再次查看数据库:
订单数据库:
库存数据库:
可以看到程序在异常的情况下,订单服务和库存服务都完成了事务的回滚,这样通过seata来实现分布式事务就圆满完成了。