一、Web应用架构
1. 单体
-
概念:整个项目所有的服务都由一台服务器提供,称为单一应用架构,也就是单机结构
-
优点:部署简单,成本低
-
缺点:
-
扩展性差,不便于协同开发,不利于升级维护
-
随着访问量的增加,单机应用负载太大,这时候就需要集群
-
2. 集群
-
概念:单机处理到达瓶颈的时候,可以把单机复制几份,这样就构成了一个集群
-
优点:通过集群来进行负载均衡,提高响应速度,实现高可用性
-
缺点:
-
当集群服务器数量到达一定程度时所带来的加速度会越来越小,此时单纯的增加服务器已无法明显提升响应速度
-
每台服务器都部署完整的功能服务,会出现小服务资源的浪费
-
3. 分布式
-
概念:
-
将项目按照业务功能拆分成多个独立的子系统,分别部署在独立的服务器上,以提升效率
-
每个子系统就被称为“服务”,这些子系统能够独立运行在Web容器中
-
服务与服务之间的调用采用
RPC
方式(Remote Procedure Call远程过程调用,是一种进程间的通信方式)
-
-
优点:通过拆分项目的业务,实现业务上的独立,降低了开发和维护的难度,便于协同开发,提高了扩展性
4. 微服务
-
概念:
-
微服务是一种架构模式,微服务架构的系统是一个分布式的系统
-
把一个大型的应用程序拆分成很多个独立的小型的应用程序,提供相应的服务
-
每个微服务完成单一的功能服务,这样的每个服务叫做一个微服务
-
每个微服务可以被独立部署,运行在自己的进程中,并使用轻量级的机制通信
-
每个微服务可以使用不同的编程语言,不同数据库,以保证最低限度的集中式管理
-
服务与服务之间的调用采用
REST
方式,其实就是HTTP方式
-
-
优点:
-
分而治之:单个服务功能内聚,复杂性低,方便团队的拆分和管理
-
可伸缩:能够单独的对指定的服务进行伸缩
-
独立部署,独立开发
-
-
缺点:微服务应用的结构较复杂,跨越多个微服务
二、SpringCloud
1. 简介
SpringCloud是一套完整的微服务解决方案,基于SpringBoot框架
SpringCloud是一系列框架的有序集合,它利用SpringBoot的开发便利性简化了分布式系统的开发
SpringCloud为开发人员提供了快速构建分布式系统的一些工具,如服务中心、配置中心、服务调用、消息总线、负载均衡、服务熔断、数据监控等
2. 技术栈
-
服务中心(服务注册与发现)
-
Eureka:目前处于停更状态,使用较少【Netflix】
-
Zookeeper:早期的一个服务中心,一般与dubbo配合使用,使用较少
-
Consul:SpringCloud官方提供的服务中心,国内使用较少
-
Nacos:阿里开发的一个组件,实现了服务中心的功能,且经过百万级并发访问的考验
-
-
配置中心
-
SpringCloud Config:SpringCloud官方提供的配置中心,比较麻烦,使用较少
-
Nacos:阿里开发的一个组件,实现了配置中心的功能
-
-
服务调用
-
Feign:目前处于停更状态,使用较少【Netflix】
-
OpenFeign:SpringCloud官方提供的服务调用组件
-
-
负载均衡
-
Ribbon:目前处于停更状态,使用较少【Netflix】
-
LoadBalancer:SpringCloud官方提供的负载均衡组件
-
-
服务熔断/服务降级
-
Hystrix:目前处于停更状态,使用较少【Netflix】
-
Sentinel:阿里开发的的服务熔断组件,服务降级/限流
-
-
服务网关
-
Zuul:目前处于停更状态,使用较少【Netflix】
-
Gateway:SpringCloud官方提供的网关
-
-
服务监控
-
Spring Boot Admin:一个管理和监控Spring Boot应用程序的开源监控软件,提供了详细的监控信息
-
-
服务链路
-
Sleuth :SpringCloud官方提供的服务链路跟踪组件
-
SkyWalking:Apache提供的服务链路跟踪组件
-
-
分布式事务
-
Seata:阿里开发的分布式事务解决方案
-
三、服务直接调用
1. 简介
微服务之间通过HTTP的方式进行互相通信,可以在某个微服务中直接调用另一个微服务,会使用REST方式
可以使用Spring提供的RestTemplate
,发送HTTP请求,直接调用另一个服务
2. 用法
步骤:
-
创建父工程cloud-parent
<packaging>pom</packaging> <modules> <module>cloud-provider-6001</module> <module>cloud-consumer-7001</module> </modules> <properties> <spring-boot.version>2.7.14</spring-boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
-
创建子工程 cloud-provider-6001
服务的提供者,构建Restful API服务
步骤:
-
添加依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
-
编辑application.yml
server: port: 6001 spring: application: name: user-provider # 服务名称
-
创建UserController
@RestController @RequestMapping("/users") public class UserController { @Value("${server.port}") int port ; @GetMapping("/test") public String test(){ return "provider:"+port; } }
-
启动并测试
-
-
创建cloud-consumer-7001
服务的消费者,访问远程的HTTP服务
步骤:
-
添加依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
-
编辑application.yml
server: port: 7001 spring: application: name: user-consumer
-
创建RestTemplateConfig配置类
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
-
创建UserController
@RestController @RequestMapping("/users") public class UserController { @Resource private RestTemplate restTemplate; @GetMapping("/test") public String test() { // 直接调用服务提供者 String str = restTemplate.getForObject("http://localhost:6001/users/test", String.class); return "consumer-->" + str; } }
-
启动并测试
-
四、Nacos
1. 简介
SpringCloud Alibaba Nacos 是一个易于构建云原生应用的动态服务发现、配置管理和服务管理平台
-
本质上是一个服务注册中心+配置中心的组合,是一个可以直接使用的软件程序
执行流程:
-
服务提供者在启动时,向注册中心注册自己提供的服务
-
服务消费者在启动时,向注册中心订阅自己所需的服务
-
注册中心返回服务提供者地址给消费者,且当发生变化时会异步通知消费者
-
服务消费者根据负载均衡算法从提供者地址中选择一个,并调用该服务提供者
2. 安装
2.1 安装操作
步骤:
-
下载
-
解压并启动
解压缩
nacos-server-2.2.3.zip
,然后进入到bin目录下启动:
-
windows:
startup.cmd -m standalone
单机模式运行 -
linux/mac:
bash startup.sh -m standalone
停止:
-
windows:
shutdown.cmd
-
linux/mac:
bash shutdown.sh
-
-
访问
通过浏览器访问 http://localhost:8848/nacos,打开nacos管理界面,默认端口是8848,默认账号和密码是
nacos
-
补充:新版nacos默认没有开启登录认证,即不需要输入账户密码
修改
conf/application.properties
文件,开启登录认证功能# 开启登录认证 nacos.core.auth.enabled=true # 客户端和服务端交互时用于加密的密钥,随意写 nacos.core.auth.server.identity.key=wanho nacos.core.auth.server.identity.value=wanho # Token认证的密钥,随意写,但长度要符合要求 nacos.core.auth.plugin.nacos.token.secret.key=wanho012345678901234567890123456789012345678901234567890123456789
2.2 外部数据库支持
Nacos支持将所有数据写入到MySQL数据库中。
步骤:
-
初始化MySQL数据库
新建一个数据库nacos,数据库初始化文件conf/mysql-schema.sql
-
修改
conf/application.properties
文件,设置MySQL数据源的相关信息spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=root db.password.0=root
-
再以单机模式启动nacos
-
测试
在nacos的配置管理中随意创建一个配置,此时会自动保存到MySQL数据库中
3. 用法
步骤:
-
配置父工程cloud-parent,添加依赖
<properties> <spring-boot.version>2.7.14</spring-boot.version> <spring-cloud.version>2021.0.8</spring-cloud.version> <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <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> </dependencies> </dependencyManagement>
-
配置子工程cloud-provider-6001
编辑pom.xml,添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
编辑application.yml,配置nacos信息
server: port: 6001 spring: application: name: user-provider cloud: nacos: discovery: # 服务注册中心 server-addr: localhost:8848 # username: nacos # password: nacos
编辑启动类,开启服务注册发现功能
@SpringBootApplication @EnableDiscoveryClient // 开启服务注册发现功能 public class CloudProvider6001Application { public static void main(String[] args) { SpringApplication.run(CloudProvider6001Application.class, args); } }
-
测试
在nacos管理界面——>服务管理中,能看到已经注册的服务
4. 心跳机制
心跳机制是用于监测和管理微服务可用性的机制,监测微服务的健康状态
-
微服务注册启动后,每隔5秒会主动向 Nacos 服务器发起心跳包(包含当前服务的名称、IP、端口、集群名、权重等)
-
服务器会根据心跳消息的到达情况和内容来判断微服务的可用性
-
如果连续15秒没有收到心跳消息,就会将该服务实例标记为不健康、不可用
-
如果连续3次没有收到客户端的心跳消息,就会将该服务实例从服务列表中移除。
五、OpenFeign和LoadBalancer
1. 简介
-
OpenFeign
-
OpenFeign是一个HTTP客户端,可以更快捷、优雅地调用HTTP服务
-
只需要创建一个接口,然后在接口上添加注解就可以了
-
-
LoadBalancer
-
LoadBalancer是一个客户端负载均衡的工具,用于实现微服务的负载均衡
-
提高响应速度,实现高可用性
-
2. 用法
步骤:
-
配置cloud-consumer-7001工程,添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
如果是新建工程,可以在创建时直接选择:OpenFeign和LoadBalancer
-
编辑application.yml文件
server: port: 7001 spring: application: name: cloud-consumer cloud: nacos: discovery: # 服务注册中心 server-addr: localhost:8848
-
创建客户端接口,调用服务提供者
@FeignClient("user-provider") // 指定要调用的服务提供者名称 public interface UserClient { @GetMapping("/users/test") public String test(); }
-
编辑UserController
@RestController @RequestMapping("/users") public class UserController { @Resource // private RestTemplate restTemplate; private UserClient userClient; @GetMapping("/test") public String consumer() { // 直接调用服务提供者 // String str = restTemplate.getForObject("http://localhost:6001/users/test", String.class); // 通过服务注册中心调用服务提供者 String str = userClient.test(); return "consumer-->" + str; } }
-
编辑启动类
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients // 开启Feign功能 public class CloudConsumer7001Application { public static void main(String[] args) { SpringApplication.run(CloudConsumer7001Application.class, args); } }
-
测试
在nacos管理界面——>服务管理中,能看到已经注册的服务
3. 负载均衡
步骤:
-
配置cloud-proiver-6001工程,编辑application.yml文件
server: port: ${PORT:6001} # 通过参数获取端口号
-
在运行配置中,复制多个cloud-proiver,并分别指定不同的端口号(参数),模拟有多个服务提供者
-
启动并测试
依次启动:
-
CloudProvider6001Application
-
CloudProvider6002Application
-
CloudProvider6003Application
-
CloudConsumer7001Application
在nacos管理界面——>服务管理中,能看到user-provider服务的实例数有3个
访问 http://localhost:7001/users/test,默认是按轮询的方式选择集群中的服务器
-
-
设置负载均衡
常见的负载均衡策略:
-
轮询 (RoundRobin) 按顺序轮流选择服务器【默认策略】
-
随机 (Random) 随机选择服务器
-
加权轮询 (Weighted RoundRobin) 根据服务器的权重来选择服务器,权重越大,被选中概率越大
-
最短响应时间 (Least Response Time) 响应时间越短的服务器被选中的可能性大
-
最小连接数 (Least Connection) 活动连接数越少的服务器被选中的可能性大
创建配置类,切换负载均衡策略:
@Configuration public class MyLoadBalancerConfig { @Bean ReactorLoadBalancer<ServiceInstance> loadBalancer(Environment environment,LoadBalancerClientFactory factory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); ObjectProvider<ServiceInstanceListSupplier> provider = factory.getLazyProvider(name, ServiceInstanceListSupplier.class); // 轮询 // return new RoundRobinLoadBalancer(provider,name); // 随机 return new RandomLoadBalancer(provider,name); } }
注:目前SpringCloud LoadBalancer的ReactiveLoadBalancer负载均衡接口,提供的实现类只有两个:随机和轮询
编辑UserClient,指定负载均衡配置:
@FeignClient("user-provider") // 指定要调用的服务提供者名称 @LoadBalancerClients(defaultConfiguration = {MyLoadBalancerConfig.class}) // 指定负载均衡配置 public interface UserClient { .... }
-
-
重启并测试
重新启动:CloudConsumer7001Application
访问 http://localhost:7001/users/test,此时按随机的方式选择集群中的服务器
-
补充:项目启动时提示如下警告信息
Spring Cloud LoadBalancer is currently working with the default cache. You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
原因:Spring Cloud LoadBalancer官方建议使用Caffeine缓存,用来对服务进行缓存,效果更高,扩展更好。
解决:添加Caffeine依赖(不要引入过高的版本,会出现兼容性问题)
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.3</version> </dependency>
-
4. 传参
OpenFeign传参的注意事项:
-
传递参数时,必须添加注解,如@RequestParam、@PathVariable、@RequestBody等
-
如果是请求体传参,必须添加@RequestBody
-
如果是普通对象,不能直接传递,使用Map传参,必须添加@RequestParam
步骤:
-
创建cloud-common
服务的通用基础类
-
配置cloud-provider-6001
-
配置cloud-consumer-7001
六、配置中心
1. 简介
在分布式应用中,将公共配置信息统一交由配置中心进行管理,如数据源配置、Redis配置等
Nacos既可以作为服务中心,也可以作为配置中心
相关术语概念:
-
Namespace 命名空间:代表不同环境,如开发、测试、生产等,默认值为
public
-
Group 组:代表某个项目,如XX电商项目、XX医疗项目等,默认值为
DEFAULT_GROUP
-
DataId 配置集:唯一标识,代表项目下某个服务的配置数据
-
命名规范为:
${spring.application.name}-${spring.profiles.active}.${file-extension}
-
项目会自动根据该命名规范读取对应的配置信息,如
user-provider-dev.yml
-
2. 用法
在cloud-provider-6001工程中,步骤:
-
编辑pom.xml文件,配置依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
-
在Nacos中添加配置信息
在nacos主页——>命名空间——>新建命名空间(shop-dev、shop-test、shop-prod)
-
命名空间ID:shop-dev
-
命名空间名:shop-dev
-
描述:shop-dev
在nacos主页——>配置管理——>配置列表——>创建配置
-
命名空间:shop-dev
-
Group:SHOP_GROUP
-
Data ID:user-provider-dev.yml
-
配置格式:YAML
-
配置内容:
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8&useSSL=false username: root password: root
-
-
将application.yml文件,改名为bootstrap.yml,并配置
使用Nacos配置中心时,建议用bootstrap.yml配置文件(引导文件)
server: port: ${PORT:6001} # 通过参数获取端口号 spring: application: name: user-provider # 服务名称 profiles: active: dev cloud: nacos: discovery: # 服务注册中心 server-addr: localhost:8848 config: # 配置中心 server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: shop-dev # 命名空间ID group: SHOP_GROUP # 组 file-extension: yml # 指定配置文件的格式
-
测试
@RestController @RefreshScope // nacos配置自动刷新 public class ConfigController { @Value("${spring.datasource.url}") private String url; @RequestMapping("/config") public String config(){ return url; } }
访问:
http://localhost:6001/config
,查看是否可以获取到远程配置中心的配置信息,且当配置中心发生变更时会自动刷新
3. 共享配置
当不同模块需要共享使用相同的配置信息时,可以在Nacos中定义共享配置,然后在每个模块中读取共享配置。
步骤:
-
在Nacos中添加共享配置信息
在nacos主页——>配置管理——>配置列表——>创建配置
-
命名空间:shop-dev
-
Group:SHOP_GROUP
-
Data ID:application-dev.yml
-
配置格式:YAML
-
配置内容:
spring: mvc: pathmatch: matching-strategy: ant_path_matcher shop: share: name: world
-
-
编辑bootstrap.yml文件
server: port: ${PORT:6001} # 通过参数获取端口号 spring: application: name: user-provider # 服务名称 profiles: active: dev cloud: nacos: discovery: # 服务注册中心 server-addr: localhost:8848 config: # 配置中心 server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: shop-dev # 命名空间ID group: SHOP_GROUP # 组 file-extension: yml # 指定配置文件的格式 shared-configs: # 共享配置 - data-id: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} group: ${spring.cloud.nacos.config.group} refresh: true # 自动刷新
-
测试
@RestController @RefreshScope // nacos配置自动刷新 public class ConfigController { @Value("${spring.datasource.url}") private String url; @Value("${shop.share.name}") private String name; @RequestMapping("/config") public String config(){ return url + "---------" + name; } }
七、Gateway
1. 简介
Gateway是API网关服务,用来保护、增强和控制对API服务的访问
Gateway提供了统一的路由方式,同时还提供了客户端负载均衡、统一认证、流量监控和速率限制等功能
Gateway中存在三大核心概念:路由、断言、过滤器
-
路由Route
-
路由是将外部的请求转发到具体的微服务实例上,是实现外部访问统一入口的基础
-
它由id、目标uri、一系列的断言与过滤器组成
-
-
断言Predicate
-
断言是作为匹配条件,可以匹配HTTP请求中的所有内容,例如:请求路径、请求头、请求参数、请求时间等
-
如果请求与断言相匹配则进行路由
-
-
过滤器Filter
-
对请求的处理过程进行干预,实现请求校验、服务聚合等功能
-
使用过滤器,可以在请求被路由前或者后进行修改
-
2. 用法
步骤:
-
创建工程cloud-gateway-5001,选择以下模块:Gateway、LoadBalancer
-
添加依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>
-
编辑application.yml文件
server: port: 5001 spring: application: name: cloud-gateway cloud: nacos: discovery: server-addr: localhost:8848 gateway: routes: # 配置路由表 - id: users_route # 路由id,必须唯一 uri: lb://user-provider # 当前路由要转发到的微服务,格式为:lb://微服务名 predicates: # 配置断言 - Path=/users/** # 指定哪些请求会进入到该路由,多个路径之间以逗号隔开
-
编辑启动类,启用Gateway
@SpringBootApplication @EnableDiscoveryClient public class CloudGateway5001Application { public static void main(String[] args) { SpringApplication.run(CloudGateway5001Application.class, args); } }
-
启动并测试
依次启动:
-
CloudProvider6001Application
-
CloudProvider6002Application
-
CloudProvider6003Application
-
CloudGateway5001Application
测试:
-
在nacos管理界面——>服务管理中,能看到
cloud-gateway
服务已经注册 -
直接向gateway发送请求进行测试 http://localhost:5001/users/test,默认是按轮询的方式
-
将cloud-consumer-7001中UserClient的服务调用改为
@FeignClient("cloud-gateway")
,然后测试接口功能
-
注:配置网关后,此时的负载均衡策略是由网关来指定的,所有请求都是交由网关来转发给后台的服务器!
3. 路由配置
为请求路径添加前缀:
spring: gateway: routes: # 配置路由表 - id: users_route # 路由id,必须唯一 uri: lb://user-provider # 当前路由要转发到的微服务,格式为:lb://微服务名 predicates: # 配置断言 # - Path=/users/** # 指定哪些请求会进入到该路由,多个路径之间以逗号隔开 - Path=/api/v2/users/** # 为请求路径添加前缀 filters: - StripPrefix=2 # 请求路径的前缀过滤,表示去掉前两个路径
测试:直接向gateway发送请求进行测试 http://localhost:5001/api/v2/users/test
配置路由的两种方式:
-
配置文件方式
spring: cloud: gateway: routes: # 配置路由表 - id: users_route # 路由id,必须唯一 uri: lb://user-provider # 当前路由要转发到的微服务,格式为:lb://微服务名 predicates: # 配置断言 # - Path=/users/** # 指定哪些请求会进入到该路由,多个路径之间以逗号隔开 - Path=/api/v2/users/** # 为请求路径添加前缀 filters: - StripPrefix=2 # 请求路径的前缀过滤,表示去掉前两个路径
-
注解方式
创建一个配置类,在该类中通过注解进行配置
@Configuration public class GatewayConfig { @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { RouteLocatorBuilder.Builder routes = builder.routes(); /** * 参数一:路由id * 参数二:访问路径及对应的远程资源地址 */ routes.route("users_route", p -> p.path("/users/**").uri("lb://user-provider")); return routes.build(); } }
4. 断言
共有12种内置的断言,常用的断言类型:Path、Method、After、Before、Between等
spring: application: name: cloud-gateway cloud: gateway: # 配置路由表 routes: - id: users_route # 路由id,必须唯一 # uri: http://127.0.0.1:8001 # 当前路由要转发到的微服务地址,此处写的是固定地址,如果是动态路由的话可以使用微服务名 uri: lb://user-provider # 使用动态路由访问集群环境,格式为:lb://微服务名 predicates: # 配置断言 # - Path=/users/** # 该断言必须配置,指定哪些请求会进入到该路由,多个路径之间以逗号隔开 - Path=/api/v2/users/** # 为请求路径添加前缀 - Method=GET,POST # 必须是指定的请求方式才能访问 filters: - StripPrefix=2 # 请求路径的前缀过滤,表示去掉前两个路径
5. 过滤器
Spring Cloud Gateway 中的Filter 分为两种类型:
-
全局过滤器 Global Filter
-
路由过滤器 Gateway Filter
5.1 全局过滤器
全局过滤器作用于所有的路由,不需要单独配置,只需要实现GlobalFilter
、Ordered
两个接口即可。
步骤:
-
自定义一个全局过滤器,实现:
网关鉴权+白名单放行
/** * 网关鉴权,实现统一的身份认证 */ @Component public class AuthFilter implements GlobalFilter, Ordered { @Resource private IgnoreWhiteProperties ignoreWhite; /** * 过滤器的核心方法 * @param exchange 用于操作请求/响应的相关信息 * @param chain 过滤器链,用于调用下一个过滤器或目标资源 * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("AuthFilter.filter................"); /** * 跳转不需要校验的白名单 */ String url = exchange.getRequest().getURI().getPath(); if (ignoreWhite.getWhites().contains(url)){ return chain.filter(exchange); // 放行 } /** * 获取token,并判断是否为空 */ String token = exchange.getRequest().getHeaders().getFirst("Authorization"); if(StringUtils.isEmpty(token)){ ServerHttpResponse res = exchange.getResponse(); String jsonStr = JSONUtil.toJsonStr(AjaxResult.error(HttpStatus.UNAUTHORIZED.value(), "请先登录再操作!")); DataBuffer buffer = res.bufferFactory().wrap( jsonStr.getBytes(StandardCharsets.UTF_8)); res.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); return res.writeWith(Mono.just(buffer)); } return chain.filter(exchange); // 放行,调用下一个过滤器或目标资源 } /** * 定义过滤器的优先级:值越小,优先级越高 */ @Override public int getOrder() { return 0; } }
-
编辑application.yml
spring: application: name: cloud-gateway cloud: gateway: routes: # 配置路由表 - id: users_route # 路由id,必须唯一 uri: lb://user-provider # 当前路由要转发到的微服务,格式为:lb://微服务名 predicates: # 配置断言 # - Path=/users/** # 指定哪些请求会进入到该路由,多个路径之间以逗号隔开 - Path=/api/v2/users/** # 为请求路径添加前缀 - Method=GET,POST # 必须是指定的请求方式才能访问 filters: - StripPrefix=2 # 请求路径的前缀过滤,表示去掉前两个路径 - BlackListUrlFilter - id: auth_route uri: lb://auth-provider predicates: - Path=/auth/** filters: - StripPrefix=1 # 不校验白名单 security: ignore: whites: - /auth/login - /auth/logout - /auth/register
5.2 路由过滤器
路由过滤器用于对指定路由进行过滤处理操作,默认内置了多种路由过滤器,如:StripPrefix去除前缀的过滤器。
自定义路由过滤器时,需要继承AbstractGatewayFilterFactory父类
步骤:
-
定义一个黑名单过滤器,用于对黑名单进行拦截,黑名单中的地址不允许访问。
/** * 黑名单过滤器 */ @Component public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUrlFilter.Config> { // 指定配置的类型 public BlackListUrlFilter(){ super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { System.out.println("BlackListUrlFilter.filter..........................."); String url = exchange.getRequest().getURI().getPath(); // if (url.equals("/users/list")){ // 如果是黑名单中的url,则拦截 if(config.getBlacklistUrl().contains(url)){ ServerHttpResponse res = exchange.getResponse(); String jsonStr = JSONUtil.toJsonStr(AjaxResult.error(HttpStatus.FORBIDDEN.value(),"请求地址不允许访问")); DataBuffer buffer = res.bufferFactory().wrap(jsonStr.getBytes(StandardCharsets.UTF_8)); res.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); return res.writeWith(Mono.just(buffer)); } return chain.filter(exchange); }; } public static class Config{ private List<String> blacklistUrl = new ArrayList<>(); public List<String> getBlacklistUrl() { return blacklistUrl; } public void setBlacklistUrl(List<String> blacklistUrl) { this.blacklistUrl = blacklistUrl; } } }
-
编辑application.yml
spring: application: name: cloud-gateway cloud: gateway: # 配置路由表 routes: - id: users_route # 路由id,必须唯一 uri: lb://user-provider # 使用动态路由访问集群环境,格式为:lb://微服务名 predicates: # 配置断言 # - Path=/users/** # 该断言必须配置,指定哪些请求会进入到该路由,多个路径之间以逗号隔开 - Path=/api/v2/users/** # 为请求路径添加前缀 - Method=GET,POST # 必须是指定的请求方式才能访问 filters: - StripPrefix=2 # 请求路径的前缀过滤,表示去掉前两个路径 # - BlackListUrlFilter # 黑名单 - name: BlackListUrlFilter args: blacklistUrl: - '/users/list' - '/users/delete' - '/users/update'
-
测试
访问 http://localhost:5001/api/v2/users/list 提示"请求地址不允许访问"
八、Sentinel
1. 简介
1.1 服务熔断和服务降级
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
服务雪崩:在微服务架构中服务之间会相互调用和依赖,如果某个服务发生故障,可能会导致多个服务故障,从而导致整个系统故障
解决服务雪崩的方式:
-
服务熔断
当服务出现不可用或响应超时时,为了防止整个系统出现雪崩, 暂时停止对该服务的调用,直接返回一个结果,快速释放资源。
如果检测到目标服务情况好转,则恢复对目标服务的调用。
-
服务降级
为了防止核心业务功能出现负荷过载或者响应慢的问题,将非核心服务进行降级,暂时性的关闭或延迟使用,保证核心服务的正常运行
1.2 Sentinel介绍
Spring Cloud Alibaba Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件。
-
主要以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。
相关术语概念:
-
资源是 Sentinel 的关键概念,它可以是 Java 应用程序中的任何内容,能够被 Sentinel 保护起来
-
规则:围绕资源的实时状态设定的规则,包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整
-
吞吐量(Throughput) :系统在单位时间内处理请求的数量
-
QPS每秒查询率(Query Per Second):每秒请求数据数量
-
响应时间(RT):平均响应时间
-
并发数: 系统同时处理的请求数量
2. 安装
步骤:
执行以下命令:
java -jar sentinel-dashboard-1.8.6.jar # 默认使用的是8080端口
以指定端口运行:
java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar
-
访问
通过浏览器访问 http://localhost:8718,打开Sentinel控制台,默认账号和密码是sentinel
3. 用法
在cloud-consumer-7001工程中,步骤:
-
编辑pom.xml文件,添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
编辑application.yml文件
server: port: 7001 spring: application: name: user-consumer cloud: nacos: discovery: server-addr: localhost:8848 # sentinel配置 sentinel: eager: true # 取消控制台懒加载,项目启动即连接sentinel transport: dashboard: localhost:8718 # 配置sentinel控制台地址 main: allow-circular-references: true # 允许循环引用
-
测试
首先发送请求,如:http://localhost:7001/users
然后在Sentinel控制台中可以看到对应的请求
4. 流控规则
4.1 简介
在Sentinel控制台中可以设置流控规则
-
资源名
-
当前为哪一个请求设置流控规则
-
默认为当前的请求地址
-
-
针对来源
-
Sentinel可以针对调用者进行限流
-
填写对应的微服务名
-
默认default(不区分来源)
-
-
阈值类型/单机阈值
-
QPS:限制每秒的请求数量,当调用的请求数达到了QPS设置的阈值,进行限流
-
并发线程数:当调用的请求的线程数达到了阈值,进行限流
-
-
是否集群
-
可以设置为不用集群
-
4.2 使用
首先为某个请求设置一个流控规则,如:限制1秒只能发送一次请求
然后访问对应的请求,如果一秒访问的请求数超过了1次,则会提示Blocked by Sentinel (flow limiting)
4.3 自定义限流异常处理
使用@SentinelResource
注解,定义出现限流异常时的处理策略,可以自定义提示消息。
-
使用当前类中的异常处理方法
/** * 使用@SentinelResource自定义限流异常处理 * value属性,表示资源名称,可以自定义,但是其值不能带有/ * blockHandlerClass属性,指定处理异常的方法所在的类,未指定时表示处理异常的方法在当前类中 * blockHandler属性,指定处理异常的方法名称 */ @SentinelResource(value = "UserListResource",blockHandler = "handlerException") @GetMapping public AjaxResult getUserList(String username){ return userClient.getUserList(username); } /** * 处理异常的方法 * 1.方法返回值需要与原方法一致 * 2.方法参数列表需要与原方法一致,同时多一个BlockException参数,表示异常对象 */ public AjaxResult handlerException(String username, BlockException e){ return AjaxResult.error("请求过于频繁,请稍后再试!参数为:" + username+",异常:"+e); }
-
使用异常处理类中的异常处理方法
public class ExceptionUtil { /** * 处理全局Sentinel异常的方法 * 该方法必须为static的 */ public static AjaxResult handlerException(String username, BlockException e){ return AjaxResult.error("Sentinel限流,请稍后再试:" + e); } } // 指定blockHandlerClass属性时,表示处理异常的方法在另一个类中,同时处理异常方法必须为static的 @SentinelResource(value = "UserListResource",blockHandlerClass = ExceptionUtil.class,blockHandler = "handlerException") @GetMapping public ResponseResult getUserList(){ return userService.getUserList(); }
5. 服务熔断
通过Sentinel实现服务熔断,其实就是一种机制
-
当某个服务不可用时,暂时停止对该服务的调用,可以阻断故障的传播,也称为断路器或熔断器
-
当出现连续多次服务调用失败时,会进行熔断保护,直接返回一个由开发者设置的fallback(退路)信息
-
会定期再次检查故障的服务,如果故障服务恢复,将继续使用服务
步骤:
-
编辑application.yml文件,启用服务熔断
# 启用sentinel熔断 feign: sentinel: enabled: true
-
设置fallback信息
在@FeignClient注解中指定fallback参数
@FeignClient(value = "cloud-gateway", fallback = UserClientFallback.class) public interface UserClient {
创建UserClient接口的实现类,并配置返回的信息
@Component public class UserClientFallback implements UserClient { @Override public String test() { return "请求失败"; } @Override public AjaxResult getUserList(String username) { System.out.println("服务熔断开启。。。。。。"); return AjaxResult.error("获取用户列表失败"); } @Override public AjaxResult deleteUser(Integer id) { return AjaxResult.error("删除用户失败"); } @Override public AjaxResult postUser(User user) { return AjaxResult.error("添加用户失败"); } @Override public AjaxResult putUser(User user) { return AjaxResult.error("修改用户失败"); } @Override public AjaxResult query(Map<String, Object> map) { return AjaxResult.error("查询用户失败"); } }
-
测试
当服务提供者不可用或出现异常时,会暂时停止对该服务的调用
6. 持久化
Sentinel结合Nacos实现持久化,使用Nacos作为数据源。
其实就是将Sentinel规则保存到Nacos中,实现规则的持久化。
步骤:
-
编辑pom.xml文件,配置依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
-
编辑application.yml文件
server: port: 7001 spring: application: name: user-consumer cloud: nacos: discovery: server-addr: localhost:8848 # sentinel配置 sentinel: transport: dashboard: localhost:8718 # sentinel控制台地址 datasource: # sentinel数据源 ds1: # 该名称自定义,此处表示限流配置 nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: sentinel-dev group-id: SENTINEL_GROUP dataId: sentinel-flow-rule data-type: json rule-type: flow # 限流规则 main: allow-circular-references: true # 允许循环引用 # 启用sentinel熔断 feign: sentinel: enabled: true
-
在Nacos中添加配置信息
在nacos主页——>配置管理中,新增配置信息
-
命名空间:sentinel-dev
-
Group:SENTINEL_GROUP
-
Data ID:sentinel-flow-rule
-
配置格式:JSON
-
配置内容:
[ { "resource":"UserListResource", "limitApp":"default", "grade":1, "count":2, "strategy":0, "controlBehavior":0, "clusterMode":false } ]
配置项说明:
-
resource:资源名称
-
limitApp:来源
-
grade:阀值类型,0---线程数,1---QPS
-
count:单机阀值
-
strategy:流控模式,0---直接,1---关联,2---链路
-
controlBehavior:流控效果,0---快速失败,1---warmUp,2---排队等待
-
clusterMode:是否集群
-
-
测试
首先发送请求,如:http://localhost:7001/users
然后在Sentinel控制台中可以看到对应的流控规则,并且限流生效
九、Gateway整合Sentinel
1. 网关限流
网关限流:就是通过网关层对服务进行限流,从而达到保护后端服务的作用。
在cloud-gateway-5001工程中,步骤:
-
编辑pom.xml文件,添加依赖
<!-- nacos-config --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!--sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- sentinel-nacos --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!--sentinel-gateway--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency>
-
将application.yml文件,改名为bootstrap.yml,并配置
连接配置中心、连接sentinel、sentinel规则持久化
server: port: 5001 spring: application: name: cloud-gateway profiles: active: dev cloud: nacos: discovery: # 服务中心 server-addr: localhost:8848 config: # 配置中心 server-addr: ${spring.cloud.nacos.discovery.server-addr} file-extension: yml sentinel: eager: true # 取消控制台懒加载 transport: dashboard: localhost:8718 # 控制台地址 datasource: # nacos配置持久化 ds1: nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} dataId: sentinel-gateway groupId: DEFAULT_GROUP data-type: json rule-type: gw-flow # 网关限流规则
-
在Nacos中添加配置信息
添加gateway配置:
-
命名空间和Group:使用默认的
public
和DEFAULT_GROUP
-
Data ID:cloud-gateway-dev.yml
-
配置格式:YAML
-
配置内容:
spring: cloud: gateway: routes: # 用户模块 - id: users_route uri: lb://user-provider predicates: - Path=/api/v2/users/** filters: - StripPrefix=2 - name: BlackListUrlFilter args: blacklistUrl: - '/users/list' - '/users/delete' - '/users/update' # 认证模块 - id: auth_route uri: lb://auth-provider predicates: - Path=/auth/** filters: - StripPrefix=1 # 不校验白名单 security: ignore: whites: - /auth/login - /auth/logout - /auth/register
添加sentinel限流配置:
-
命名空间和Group:使用默认的
public
和DEFAULT_GROUP
-
Data ID:sentinel-gateway
-
配置格式:JSON
-
配置内容:
[ { "resource": "users_route", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0 } ]
注:资源名resource为Route ID,根据Route ID设置限流,一般是对某个微服务进行限流。
-
-
编辑启动类,标记该应用为API网关类型,即网关流控
@SpringBootApplication @EnableDiscoveryClient public class CloudGateway5001Application { public static void main(String[] args) { System.setProperty("csp.sentinel.app.type", "1"); // 标记该应用为API网关类型 SpringApplication.run(CloudGateway5001Application.class, args); } }
-
测试
在Sentinel控制台中可以看到对应的流控规则
直接向gateway发送请求,进行流控的测试:http://localhost:5001/api/v2/users
2. 限流异常处理
定义一个限流异常处理类,实现 WebExceptionHandler 接口
/** * 自定义限流异常处理 */ @Order(Ordered.HIGHEST_PRECEDENCE) @Configuration public class SentinelFallbackHandler implements WebExceptionHandler { private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) { ServerHttpResponse res = exchange.getResponse(); String jsonStr = JSONUtil.toJsonStr(AjaxResult.error(HttpStatus.TOO_MANY_REQUESTS.value(), "请求超过最大数,请稍候再试")); DataBuffer buffer = res.bufferFactory().wrap(jsonStr.getBytes(StandardCharsets.UTF_8)); res.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); return res.writeWith(Mono.just(buffer)); } @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { if (exchange.getResponse().isCommitted()) { return Mono.error(ex); } if (!BlockException.isBlockException(ex)) { return Mono.error(ex); } return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange)); } private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); } }
3. 网关异常处理
定义一个网关异常处理类,实现 ErrorWebExceptionHandler 接口
/** * 网关统一异常处理 */ @Order(-1) @Configuration public class GatewayExceptionHandler implements ErrorWebExceptionHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { ServerHttpResponse res = exchange.getResponse(); String jsonStr = JSONUtil.toJsonStr(AjaxResult.error(ex.getMessage())); DataBuffer buffer = res.bufferFactory().wrap(jsonStr.getBytes(StandardCharsets.UTF_8)); res.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); return res.writeWith(Mono.just(buffer)); } }
十、Seata
1. 简介
分布式事务:
-
指一次大的操作由不同的小操作组成的,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败。从本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
Seata分布式事务:
-
Seata
是阿里开发的一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。 -
Seata
提供了四种事务模式:AT模式(推荐)、TCC模式、Saga模式、XA模式 -
参考:Apache Seata
2. 术语
在Seata的架构中,一共有三个角色:
-
TC(Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
-
TM(Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
-
RM(Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata架构图如下所示:
-
TM 和 RM 是作为
Seata客户端
与业务系统集成在一起 -
TC 作为
Seata服务端
独立部署。
3. 执行流程
在 Seata 中,分布式事务的执行流程:
-
TM 开启分布式事务(TM 向 TC 注册全局事务记录);
-
按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
-
TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
-
TC 汇总事务信息,决定分布式事务是提交还是回滚;
-
TC 通知所有 RM 提交/回滚 资源,事务二阶段结束;
注:整个过程是分为两个阶段来实现的,即成功则提交,失败则回滚。
4. AT模式
AT模式(Autonomous Transaction)是Seata独有的一种模式,通过自动补偿机制实现数据回滚,也是实际项目中比较常用的一种模式。
原理:
-
通过生成反向SQL实现数据回滚,需要在数据库中额外添加undo_log表,undo_log表中保存的是自动生成的回滚SQL。
示例:
-
业务逻辑要执行的sql
insert into 订单 values(1001,...) update 仓储 set num = 300 where gid =100;
-
自动生成回滚sql
delete from 订单 where id =1001 update 仓储 set num=210 where gid = 100
特点:
-
效率高,使用简单(Seata自动生成反向SQL并回滚)
-
对业务无侵入
-
需要注意的是,所有服务与数据库必须要自己拥有管理权,因为要创建undo_log表
5. 安装
Seata服务端也是一个微服务,和其他微服务一样也需要注册中心
和配置中心
,同时需要记录事务相关信息,可以将其存储到数据库中。
步骤:
-
下载并解压Seata
https://seata.io/zh-cn/blog/download.html
seata-server-1.7.0.zip
-
修改Seata配置文件
进入到
seata/conf
目录下,编辑application.yml文件,内容如下:server: port: 7091 spring: application: name: seata-server logging: config: classpath:logback-spring.xml file: path: ${user.home}/logs/seata extend: logstash-appender: destination: 127.0.0.1:4560 kafka-appender: bootstrap-servers: 127.0.0.1:9092 topic: logback_to_logstash # seata控制台账号密码 console: user: username: seata password: seata # seata服务端配置 seata: service: vgroupMapping: default-tx-group: default # 事务组名: 集群名,都可以自定义 config: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: group: SEATA_GROUP registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 namespace: group: SEATA_GROUP cluster: default # 集群名 store: mode: db db: datasource: druid db-type: mysql driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/ry-seata?useUnicode=true&characterEncoding=utf8&useSSL=false user: root password: root min-conn: 10 max-conn: 100 global-table: global_table branch-table: branch_table lock-table: lock_table distributed-lock-table: distributed_lock query-limit: 1000 max-wait: 5000 security: secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 tokenValidityInMilliseconds: 1800000 ignore: urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
-
初始化MySQL数据库
新建一个数据库ry-seata,数据库初始化文件
script/server/db/mysql.sql
-
global_table: 全局事务表,每当有一个全局事务发起后,就会在该表中记录全局事务的ID。
-
branch_table: 分支事务表,记录每一个分支事务的 ID,分支事务操作的哪个数据库等信息。
-
lock_table: 全局锁表。
-
distributed_lock:异步任务锁表,seata1.5之后新增的表,用于seate-server异步任务调度
-
-
启动Seata
进入到seata/bin目录下:
-
windows:双击
seata-server.bat
即可启动 -
linux/mac:执行
bash seata-server.sh
命令
-
-
访问
通过浏览器访问 http://localhost:7091,打开Seata控制台界面,默认端口是7091,默认账号和密码是
seata
在nacos管理界面——>服务管理中,能看到已经注册的
seata-server
服务注:Seata启动时会占用两个端口:7091 和 8091
-
7091是客户端端口
-
8091是注册到nacos中的服务端端口
-
6. 用法
步骤:
-
在Nacos中添加事务组的信息
-
命名空间:使用默认的
public
-
Group:SEATA_GROUP
-
Data ID:service.vgroupMapping.default-tx-group
-
配置格式:TEXT
-
配置内容:
default
注:service.vgroupMapping.xxx-xxx-xxx中的
xxx-xxx
为事务组名,配置内容为集群名 -
-
添加依赖
<!-- 分布式事务seata --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-common-seata</artifactId> </dependency>
-
修改各服务模块的配置
凡是需要使用分布式事务的模块都需要修改!
spring: datasource: dynamic: seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭 # seata配置 seata: enabled: true # 事务组名 tx-service-group: default-tx-group # 关闭自动代理 enable-auto-data-source-proxy: false # 服务配置项 service: vgroup-mapping: default-tx-group: default config: type: nacos nacos: serverAddr: 127.0.0.1:8848 group: SEATA_GROUP namespace: registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 namespace:
-
各数据库中添加undo_log表
凡是需要使用分布式事务的模块,对应的数据库中都需要添加该表!
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, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
在主服务中添加seata全局事务注解@GlobalTransactional
调用其他服务模块的称为主服务方,即第一个开启事务的
/** * 新增spu信息 */ @Override @GlobalTransactional(rollbackFor = Exception.class) // 重点:第一个开启事务的需要添加seata全局事务注解 public void insertSpuInfo(ProductDTO productDTO) { log.info("Seata全局事务开始..........全局事务id = " + RootContext.getXID()); // ..... }
-
在其他服务中添加事务注解
被调用的服务模块都是,即非第一个开启事务的
/** * 新增商品阶梯价格 */ @Override @Transactional(propagation = Propagation.REQUIRES_NEW) // 一定要使用REQUIRES_NEW,开启新事务 public int insertSkuLadder(SkuLadder skuLadder) { log.info("在分支事务中获取全局事务id = " + RootContext.getXID()); return skuLadderMapper.insertSkuLadder(skuLadder); }
/** * 新增商品满减信息 */ @Override @Transactional(propagation = Propagation.REQUIRES_NEW) // 一定要使用REQUIRES_NEW,开启新事务 public int insertSkuFullReduction(SkuFullReduction skuFullReduction) { return skuFullReductionMapper.insertSkuFullReduction(skuFullReduction); }
/** * 新增商品会员价格 */ @Override @Transactional(propagation = Propagation.REQUIRES_NEW) // 一定要使用REQUIRES_NEW,开启新事务 public int insertMemberPrice(MemberPrice memberPrice) { return memberPriceMapper.insertMemberPrice(memberPrice); }
/** * 新增商品spu积分设置 */ @Override @Transactional(propagation = Propagation.REQUIRES_NEW) // 一定要使用REQUIRES_NEW,开启新事务 public int insertSpuBounds(SpuBounds spuBounds) { return spuBoundsMapper.insertSpuBounds(spuBounds); }
-
测试
当出现异常时会回滚。
设置断点,在Seata控制台界面查看事务信息。
7. 交互流程梳理
Seata涉及到三个角色之间的交互,通过流程图将AT模式下的TM、RM、TC的交互流程梳理一下。
假设有三个微服务,分别是服务A、B、C,其中服务A中调用了服务B和服务C,TM、TC、RM三者之间的交互流程如下:
-
服务A启动时,GlobalTransactionScanner会对有@GlobalTransaction注解的方法进行AOP增强,并生成代理,增强的代码位于GlobalTransactionalInterceptor类中,当调用@GlobalTransaction注解的方法时,增强代码首先向TC注册全局事务,表示全局事务的开始,同时TC生成XID,并返回给TM;
-
服务A中调用服务B时,将XID传递给服务B;
-
服务B得到XID后,访问TC,注册分支事务,并从TC获得分支事务ID,TC根据XID将分支事务与全局事务关联;
-
接下来服务B开始执行SQL语句,在执行前将表中对应的数据保存一份,执行后再保存一份,将这两份记录作为回滚记录写入到数据库中,如果执行过程中没有异常,服务B最后将事务提交,并通知TC分支事务成功,服务B也会清除本地事务数据;
-
服务A访问完服务B后,访问服务C;
-
服务C与TC之间的交互与服务B完全一致;
-
服务B和服务C都成功后,服务A通过TM通知TC全局事务成功,如果失败了,服务A也会通知TC全局事务失败;
-
TC记录了全局事务下的每个分支事务,TC收到全局事务的结果后,如果结果成功,则通知RM成功,RM收到通知后清理之前在数据库中保存的回滚记录,如果失败了,则RM要查询出之前在数据库保存的回滚记录,对之前的SQL操作进行回滚。
因为TM、RM、TC之间的交互都是通过网络完成的,很容易出现网络断开的情况,因此TC提供了四个定时线程池,定时检测系统中是否有超时事务、异步提交事务、回滚重试事务、重试提交事务,如果发现了有这四类事务,则从全局事务中获取所有的分支事务,分别调用各个分支事务完成对应的操作,依次来确保事务的一致性。