SpringCloud

image-20230210111413170

image-20230210111437287

image-20230210111840848

微服务

服务架构演变

单体架构

将业务的所有功能集中在一个项目中开发并打包部署

  • 优点:架构简单、部署成本低
  • 缺点:耦合度高

image-20230210112345474

分布式架构

根据业务功能对系统进行拆分,每个业务模块作为一个独立项目开发

  • 优点:降低服务耦合,利于服务升级拓展

image-20230210112702501

  • 问题

    image-20230210112951592

    • 服务拆分的粒度
    • 服务地址的维护
    • 服务远程的调用
    • 服务状态的感知

微服务

微服务是一种经过良好架构设计的分布式架构设计方案。

image-20230210113807145

微服务架构的特征:

  • 单一职责:微服务拆分粒度更小,每个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:每个微服务对外暴露业务接口
  • 独立自治:开发独立,技术独立,数据独立,部署独立
  • 级联隔离:服务调用要做好隔离、容错、降级,避免出现级联问题

微服务的优缺点:

  • 优点:拆分粒度更小,服务更加独立,耦合度更低
  • 缺点:架构非常复杂,运维、监控、部署难度高

微服务技术对比:

image-20230210125543588

SpringCloud

image-20230210130124308

image-20230210130153060

SpringCloud集成了各种微服务组件,并基于SpringBoot实现组件的自动装配,从而提供开箱即用的体验

image-20230210130814678

SpringCloud和SpringBoot版本对应关系

image-20230211180656324

服务拆分

  • 不同微服务单一职责,不会重复开发相同的业务
  • 不同微服务数据独立,不会访问其他服务的数据库
  • 微服务将业务暴露为接口,供其他微服务调用

远程调用

RestTemplate

基于RestTemplate发起的http请求实现的远程调用与语言就无关,只要请求的ip地址、端口、接口路径以及请求参数即可。

导入Ribbon依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
注册RestTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class RestTemplateConfig {
/**
* 注册RestTemplate对象
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

使用RestTemplate发送http请求
1
2
// 使用RestTemplate发送http请求
UmsMember umsMember = restTemplate.getForObject("http://localhost:8001/member/" + omsOrder.getMemberId(), UmsMember.class);

提供者与消费者

image-20230215174602110

  • 服务提供者:暴露接口给其他微服务调用
  • 服务消费者:调用其他微服务提供的接口
  • 提供者与消费者角色是相对的

Eureka

RestTemplate调用出现的问题

  • 硬编码方式不易于修改请求地址
  • 集群环境不能获取地址和负载均衡
  • 不能监测服务提供者的健康状态

Eureka的作用

Spring Cloud Eureka是Spring Cloud Netflix 子项目的核心组件之一,主要用于微服务架构中的服务治理。本文将对搭建Eureka注册中心,搭建Eureka客户端,搭建Eureka集群及给Eureka注册中心添加登录认证进行介绍。

在微服务架构中往往会有一个注册中心,每个微服务都会向注册中心去注册自己的地址及端口信息,注册中心维护着服务名称与服务实例的对应关系。每个微服务都会定时从注册中心获取服务列表,同时汇报自己的运行情况,这样当有的服务需要调用其他服务时,就可以从自己获取到的服务列表中获取实例地址进行调用,Eureka实现了这套服务注册与发现机制。

image-20230215180145676

image-20230215180245207

image-20230215180306986

搭建Eureka服务端

父工程引入spring-cloud依赖

1
2
3
4
5
6
7
8
<!--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>

创建子工程

img

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

启动类添加@EnableEurekaServer启用Eureka注册中心

1
2
3
4
5
6
7
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

配置注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 7001 # 指定运行端口

spring:
application:
name: eureka-server # 指定服务名称

eureka:
instance:
hostname: localhost # 指定主机地址
client:
fetch-registry: false # 指定是否要从注册中心获取服务(注册中心不需要开启)
register-with-eureka: false # 指定是否要注册到注册中心(注册中心不需要开启)
server:
enable-self-preservation: false # 关闭保护模式

运行并访问http://localhost:7001/

image-20230216110000487

搭建Eureka客户端

引入依赖

1
2
3
4
5
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

启动类添加@EnableDiscoveryClient启用Eureka服务发现

1
2
3
4
5
6
7
@EnableDiscoveryClient
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}

配置编辑

1
2
3
4
5
6
7
# eureka 服务注册与发现配置
eureka:
client:
register-with-eureka: true # 注册到Eureka的注册中心
fetch-registry: true # 获取注册实例列表
service-url:
defaultZone: http://localhost:7001/eureka/ # 配置注册中心地址

查看注册信息

image-20230216111133843

多实例部署

多实例部署,修改端口设置,避免端口冲突

1
-Dserver.port=8002

image-20230216144158442

image-20230216145444136

拉取服务

1
2
// 使用RestTemplate发送http请求
CommonResult commonResult = restTemplate.getForObject("http://demo-user/member/" + omsOrder.getMemberId(), CommonResult.class);

负载均衡

1
2
3
4
5
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

Eureka集群搭建

由于所有服务都会注册到注册中心去,服务之间的调用都是通过从注册中心获取的服务列表来调用,注册中心一旦宕机,所有服务调用都会出现问题。所以我们需要多个注册中心组成集群来提供服务,下面将搭建一个双节点的注册中心集群。

通过多个注册中心互相注册,搭建了注册中心的多节点集群。

编辑配置文件

  • application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 7001 #指定运行端口

spring:
application:
name: eureka-server #指定服务名称

eureka:
instance:
hostname: localhost #指定主机地址
client:
serviceUrl:
defaultZone: http://localhost:7002/eureka/, http://localhost:7003/eureka/ #注册到另一个Eureka注册中心
fetch-registry: true #指定是否要从注册中心获取服务
register-with-eureka: true #指定是否要注册到注册中心
  • application-replica1.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 7002

spring:
application:
name: eureka-server

eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:7001/eureka/, http://localhost:7003/eureka/
fetch-registry: true
register-with-eureka: true
  • application-replica2.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 7003

spring:
application:
name: eureka-server

eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:7001/eureka/, http://localhost:7002/eureka/
fetch-registry: true
register-with-eureka: true

复制运行配置

image-20230216173000337

编辑配置文件

image-20230216172820478

Eureka添加认证

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

配置WebSecurityConfig

默认情况下添加SpringSecurity依赖的应用每个请求都需要添加CSRF token才能访问,Eureka客户端注册时并不会添加,所以需要配置/eureka/**路径不需要CSRF token。

1
2
3
4
5
6
7
8
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}

编辑配置文件

配置文件中需要修改注册中心地址格式

1
http://${username}:${password}@${hostname}:${port}/eureka/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 7003

spring:
application:
name: eureka-server
security:
user:
name: admin
password: 123456

eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://admin:123456@localhost:7001/eureka/, http://admin:123456@localhost:7002/eureka/
fetch-registry: true
register-with-eureka: true

image-20230216174959224

image-20230216175016142

Eureka常见配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
eureka:
client: #eureka客户端配置
register-with-eureka: true #是否将自己注册到eureka服务端上去
fetch-registry: true #是否获取eureka服务端上注册的服务列表
service-url:
defaultZone: http://localhost:8001/eureka/ # 指定注册中心地址
enabled: true # 启用eureka客户端
registry-fetch-interval-seconds: 30 #定义去eureka服务端获取服务列表的时间间隔
instance: #eureka客户端实例配置
lease-renewal-interval-in-seconds: 30 #定义服务多久去注册中心续约
lease-expiration-duration-in-seconds: 90 #定义服务多久不去续约认为服务失效
metadata-map:
zone: jiangsu #所在区域
hostname: localhost #服务主机名称
prefer-ip-address: false #是否优先使用ip来作为主机名
server: #eureka服务端配置
enable-self-preservation: false #关闭eureka服务端的保护机制

总结

搭建Eureka服务端

  • 引入eureka-server依赖
  • 添加@EnableEurekaServer注解
  • 在application.yml配置eureka地址

搭建Eureka服务端

  • 引入eureka-client依赖
  • 添加@EnableDiscoveryClient注解
  • 在application.yml配置eureka地址

服务发现

  • RestTemplate添加@LoadBalanced注解
  • 用服务提供者的服务名称远程调用

image-20230216171238673

Ribbon

Spring Cloud Ribbon 是Spring Cloud Netflix 子项目的核心组件之一,主要给服务间调用及API网关转发提供负载均衡的功能。

在微服务架构中,很多服务都会部署多个,其他服务去调用该服务的时候,如何保证负载均衡是个不得不去考虑的问题。负载均衡可以增加系统的可用性和扩展性,当我们使用RestTemplate来调用其他服务时,Ribbon可以很方便的实现负载均衡功能。

image-20230216175448616

image-20230217102436638

负载均衡流程

image-20230217104237779

负载均衡策略

image-20230217104059033

Ribbon的负载均衡规则由一个IRule的接口定义,每一个接口都是一种规则

image-20230217105006354

image-20230217105159529

所谓的负载均衡策略,就是当A服务调用B服务时,此时B服务有多个实例,这时A服务以何种方式来选择调用的B实例,ribbon可以选择以下几种负载均衡策略。

  • com.netflix.loadbalancer.RandomRule:从提供服务的实例中以随机的方式;
  • com.netflix.loadbalancer.RoundRobinRule:以线性轮询的方式,就是维护一个计数器,从提供服务的实例中按顺序选取,第一次选第一个,第二次选第二个,以此类推,到最后一个以后再从头来过;
  • com.netflix.loadbalancer.RetryRule:在RoundRobinRule的基础上添加重试机制,即在指定的重试时间内,反复使用线性轮询策略来选择可用实例;
  • com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择;
  • com.netflix.loadbalancer.BestAvailableRule:选择并发较小的实例;
  • com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
  • com.netflix.loadbalancer.ZoneAwareLoadBalancer:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例。

Ribbon常用配置

全局配置

1
2
3
4
5
6
7
ribbon:
ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
OkToRetryOnAllOperations: true #对超时请求启用重试机制
MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
MaxAutoRetries: 1 # 切换实例后重试最大次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法

指定服务配置

1
2
3
4
5
6
7
8
user-service:
ribbon:
ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
OkToRetryOnAllOperations: true #对超时请求启用重试机制
MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
MaxAutoRetries: 1 # 切换实例后重试最大次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法

修改负载均衡算法

定义新IRule并注入Bean

1
2
3
4
@Bean
public IRule randomRule() {
return new RandomRule();
}

修改配置文件

1
2
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法

饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会创建LoadBalanceClient负载均衡客户端,请求时间会较长。

饥饿加载会在项目启动时创建,降低第一次访问的耗时。

开启饥饿加载

1
2
3
4
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定饥饿加载的服务

总结

image-20230217141915851

Nacos

Nacos简介

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案,Nacos 作为其核心组件之一,可以作为注册中心配置中心使用,本文将对其用法进行详细介绍。

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 具有如下特性:

  • 服务发现和服务健康监测:支持基于DNS和基于RPC的服务发现,支持对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求;
  • 动态配置服务:动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置;
  • 动态 DNS 服务:动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务;
  • 服务及其元数据管理:支持从微服务平台建设的视角管理数据中心的所有服务及元数据。

Nacos安装

拉取Nacos镜像

1
docker pull nacos/nacos-server

运行Nacos容器

1
docker run --env MODE=standalone --name nacos -d -p 8848:8848 nacos/nacos-server

访问地址

http://xx.xx.xx.xxx:8848/nacos/

初始账号和密码:nacos/nacos

应用注册

添加SpringCloudAlibaba依赖

1
2
3
4
5
6
7
8
9
10
11
<!--Spring Cloud Alibaba 相关依赖-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

添加Nacos服务发现依赖

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

修改配置文件

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址

服务分级存储模型

服务调用尽量选择本地集群的服务,跨地域集群调用延迟较高。

使用服务分级存储模型,当本地集群不可访问时,再去访问其他集群。

image-20230217173645935

修改配置文件

添加spring.cloud.nacos.discovery.cluster-name属性

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
cluster-name: JIANGSU # 集群名称

复制运行配置

image-20230217175125981

image-20230217174723828

集群负载均衡

设置负载均衡IRule为NacosRule

1
2
3
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule

NacosRule负载均衡策略

  • 优先选择同集群服务实例列表
  • 本地集群无法找到服务提供者,才去其他集群寻找并报告警告
  • 确定可用实例列表,采用随机负载均衡策略挑选实例

权重负载均衡

由于服务器设备性能存在差异,部分实例所在机器性能较好,需要承担更多的用户请求。

Nacos提供了权重配置来控制访问频率,权重越大,访问频率越高。

image-20230220134417692

实例权重控制

  • Nacos控制台可以设置实例的权重值(0-1之间)
  • 同集群内的多个实例,权重越高被访问的频率越高
  • 权重设置为0,则完全不会被访问

环境隔离

Nacos中服务存储和数据存储的最外层是namespace,用来最外层隔离,隔离不同环境。

image-20230220134903522

创建命名空间

image-20230220135549579

image-20230220135606369

修改配置文件

1
2
3
4
cloud:
nacos:
discovery:
namespace: 3c30a7da-2359-490d-90f3-c67f0f64d477 # namespace 命名空间ID

image-20230220140152505

启动服务

image-20230220140325330

Nacos配置中心

Nacos和Eureka的区别

image-20230220141231444

临时实例和非临时实例

配置非临时实例
1
2
3
4
5
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置是否为临时实例

临时实例宕机时,从nacos的服务列表中剔除,而非实例则不会

image-20230220155502822

总结

image-20230220142706896

Nacos与Eureka的共同点
  • 支持服务注册和服务拉取
  • 支持服务提供者以心跳方式做健康检测
Nacos与Eureka的区别点
  • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  • 临时实例心跳不正常会被剔除,非临时实例不正常时不会被剔除
  • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

配置管理

统一配置管理

image-20230220163338981

添加配置

Nacos的dataid的组成格式以及springboot配置文件中属性对应关系

1
2
# 服务名称-配置版本.文件格式
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

image-20230220164853919

获取配置步骤

image-20230220170309580

引入Nacos配置管理客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<!-- Bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
添加配置文件bootstrap.yml

bootstrap.yml文件是引导文件,优先级高于application.yml,主要对Nacos的作为配置中心的功能进行配置

image-20230220171219558

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: demo-order # 服务名称
profiles:
active: dev # 开发环境
cloud:
nacos:
discovery:
server-addr: 1.117.34.49:8848 # Nacos 地址
config:
server-addr: 1.117.34.49:8848 # Nacos地址
file-extension: yaml # 获取的yaml格式的配置
创建ConfigClientController从Nacos配置中心获取配置信息
1
2
3
4
5
6
7
8
9
10
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;

@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}

image-20230221135527778

image-20230221132156988

配置热更新

Nacos中的配置文件变更后,微服务无需重启就可以感知

  • 方式一:在@Value注入变量所在类上添加注解@RefreshScope

image-20230221140434067

  • 方式二:使用@ConfigurationProperties注解
1
2
3
4
5
6
@Data
@Component
@ConfigurationProperties(prefix = "config")
public class ConfigInfoProperties {
private String info;
}
1
2
3
4
5
6
7
8
9
10
@RestController
public class ConfigClientController {
@Resource
private ConfigInfoProperties configInfoProperties;

@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfoProperties.getInfo();
}
}

image-20230221141822603

多环境配置共享

项目启动时会从Nacos读取多个配置文件,无论profile如何变化,[spring.application.name].[spring.cloud.nacos.config.file-extension]一定会被加载

  • ```yml
    ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
    1
    2
    3

    - ```
    ${spring.application.name}.${spring.cloud.nacos.config.file-extension}

image-20230221142231467

image-20230221142933061

1
2
3
4
5
6
7
8
@Data
@Component
@ConfigurationProperties(prefix = "config")
public class ConfigInfoProperties {
private String info;
private String shared;
}

1
2
3
4
5
6
7
@RestController
public class ConfigClientController {
@GetMapping("/shared")
public String shared() {
return configInfoProperties.getShared();
}
}

image-20230221144050665

image-20230221143743961

多种配置的优先级
  • 服务名称-profile.yaml > 服务名称.yaml > 本地配置

image-20230221144537596

image-20230221145027139

Nacos集群搭建

img

image-20230221145236903

Nacos节点
节点 ip地址 port
nacos1 1.117.34.49 8845
nacos2 1.117.34.49 8846
nacos3 1.117.34.49 8847
集群搭建
  • 搭建数据库

image-20230221151411726

  • 容器启动Nacos集群

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
version: '3' 
services:
docker-nacos-server-1:
image: nacos/nacos-server
container_name: nacos-server-1
ports:
- "8845:8848"
- "9555:9555"
restart: on-failure
network_mode: host
environment:
SPRING_DATASOURCE_PLATFORM: mysql
NACOS_SERVERS: 1.117.34.49:8845 1.117.34.49:8846 1.117.34.49:8847
NACOS_APPLICATION_PORT: 8845
MYSQL_SERVICE_HOST: 1.117.34.49
MYSQL_SERVICE_PORT: 3306
MYSQL_SERVICE_DB_NAME: nacos_config
MYSQL_SERVICE_USER: root
MYSQL_SERVICE_PASSWORD: root
JVM_XMS: 256m
JVM_XMX: 256m
JVM_XMN: 256m
volumes:
- /mydata/nacos-cluster/nacos1/logs:/home/nacos/logs
- /mydata/nacos-cluster/nacos1/init.d/custom.properties:/home/nacos/init.d/custom.properties
docker-nacos-server-2:
image: nacos/nacos-server
container_name: nacos-server-2
ports:
- "8846:8848"
restart: on-failure
network_mode: host
environment:
SPRING_DATASOURCE_PLATFORM: mysql
NACOS_SERVERS: 1.117.34.49:8845 1.117.34.49:8846 1.117.34.49:8847
NACOS_APPLICATION_PORT: 8845
MYSQL_SERVICE_HOST: 1.117.34.49
MYSQL_SERVICE_PORT: 3306
MYSQL_SERVICE_DB_NAME: nacos_config
MYSQL_SERVICE_USER: root
MYSQL_SERVICE_PASSWORD: root
JVM_XMS: 256m
JVM_XMX: 256m
JVM_XMN: 256m
volumes:
- /mydata/nacos-cluster/nacos2/logs:/home/nacos/logs
- /mydata/nacos-cluster/nacos2/init.d/custom.properties:/home/nacos/init.d/custom.properties
docker-nacos-server-3:
image: nacos/nacos-server
container_name: nacos-server-3
ports:
- "8847:8848"
restart: on-failure
network_mode: host
environment:
SPRING_DATASOURCE_PLATFORM: mysql
NACOS_SERVERS: 1.117.34.49:8845 1.117.34.49:8846 1.117.34.49:8847
NACOS_APPLICATION_PORT: 8845
MYSQL_SERVICE_HOST: 1.117.34.49
MYSQL_SERVICE_PORT: 3306
MYSQL_SERVICE_DB_NAME: nacos_config
MYSQL_SERVICE_USER: root
MYSQL_SERVICE_PASSWORD: root
JVM_XMS: 256m
JVM_XMX: 256m
JVM_XMN: 256m
volumes:
- /mydata/nacos-cluster/nacos3/logs:/home/nacos/logs
- /mydata/nacos-cluster/nacos3/init.d/custom.properties:/home/nacos/init.d/custom.properties
  • nginx反向代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upstream nacos-cluster {
server 1.117.34.49:8845;
server 1.117.34.49:8846;
server 1.117.34.49:8847;
}

server {
listen 80;
listen [::]:80;
server_name localhost;

location /nacos {
proxy_pass http://nacos-cluster
}
}

image-20230221161910758

Feign

远程调用

RestTemplate调用存在的问题

1
2
CommonResult commonResult = restTemplate.getForObject("http://demo-user/member/" + omsOrder.getMemberId(), CommonResult.class);
UmsMember umsMember = BeanUtil.toBean(commonResult.getData(), UmsMember.class);
  • 代码可读性和编程体验性差
  • URL和参数难以维护

Fegin介绍

Spring Cloud OpenFeign 是声明式的服务调用工具,它整合了Ribbon和Hystrix,拥有负载均衡和服务容错功能。

Feign是声明式的服务调用工具,我们只需创建一个接口并用注解的方式来配置它,就可以实现对某个服务接口的调用,简化了直接使用RestTemplate来调用服务接口的开发量。Feign具备可插拔的注解支持,同时支持Feign注解、JAX-RS注解及SpringMVC注解。当使用Feign时,Spring Cloud集成了Ribbon和Eureka以提供负载均衡的服务调用及基于Hystrix的服务容错保护功能。

添加依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类添加@EnableFeignClients来启动Feign客户端功能
1
2
3
4
5
6
7
8
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
添加UserService接口完成服务接口绑定

我们通过@FeignClient注解实现了一个Feign客户端,其中的value为user-service表示这是对demo-user服务的接口调用客户端。我们可以回想下user-service中的UserController,只需将其改为接口,保留原来的SpringMvc注释即可。

1
2
3
4
5
@FeignClient("demo-user")
public interface UserService {
@GetMapping(value = "/member/{id}")
CommonResult detail(@PathVariable Long id);
}
调用UserService实现服务调用
1
2
CommonResult commonResult = userService.detail(omsOrder.getMemberId());
UmsMember umsMember = BeanUtil.toBean(commonResult.getData(), UmsMember.class);

image-20230221174553914

自定义配置

Feign自定义配置覆盖默认配置

image-20230221203110311

类型 作用 说明
feign.Logger.Level 修改日志级别 四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder 响应结果的解析器 解析HTTP远程调用的返回结果
feign.codec.Encoder 请求参数编码 编码HTTP远程调用的请求参数
feign.Contract 支持的注解格式 默认是SpringMVC注解
feign.Retryer 失败重试机制 请求失败的重试机制,默认没有,使用Ribbon的重试机制

Feign自定义配置

配置方式
  • 全局配置
1
2
3
4
5
feign:
client:
config:
default: # 用default是全局配置,用服务名称是局部服务配置
loggerLevel: FULL # 日志级别
  • 局部配置
1
2
3
4
5
feign:
client:
config:
demo-user: # 用default是全局配置,用服务名称是局部服务配置
loggerLevel: FULL # 日志级别
Bean方式

配置类声明一个Bean

1
2
3
4
5
6
public class FeignClientConfiguration {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
  • 全局配置,启动类@EnableFeignClient注解中
1
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
  • 局部配置,绑定接口@FeignClient注解
1
@FeignClient(value = "demo-user", configuration = FeignClientConfiguration.class)

image-20230222112112606

image-20230222113524115

性能优化

Feign底层客户端实现

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池

优化Feign的性能主要包括

  • 使用连接池代替默认的URLConnection
  • 日志级别,最好使用basic或none

添加HttpClient的支持

引入依赖
1
2
3
4
5
<!-- HttpClient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
配置连接池
1
2
3
4
5
feign:
httpclient:
enabled: true # 开启Feign对HttpClient的支持
max-connections: # 最大连接数
max-connections-per-route: 50 # 每个路径的最大连接数

最佳实践

  • 方式一-继承:给消费者的FeignClient和提供者的Controller定义统一的父接口作为标准

image-20230222141439427

  • 方式二-抽取:将FeignClient抽取为独立通用模块,默认的Feign都放到这个模块中提供给消费者使用

image-20230222141736918

image-20230222142305225

image-20230222141906745

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用

  • 方法一:指定FeignClient所在包
1
@EnableFeignClients(basePackages = "com.example.demo.feign")
  • 方法二:指定FeignClient字节码
1
@EnableFeignClients(clients={UserService.class})

image-20230222143607565

服务降级

Feign中的服务降级只需要Feign客户端定义的接口添加一个服务降级处理的实现类即可

导入依赖

1
2
3
4
5
<!-- Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

添加服务降级实现类UserFallbackService

继承接口并对接口中的每个实现方法进行了服务降级逻辑的实现

1
2
3
4
5
6
7
8
@Component
public class UserFallbackService implements UserService {
@Override
public CommonResult detail(Long id) {
UmsMember defaultMember = new UmsMember();
return CommonResult.success(defaultMember);
}
}

设置服务降级处理类为UserFallbackService

修改@FeignClient注解中的参数,设置fallback为UserFallbackService.class即可。

1
2
3
4
5
@FeignClient(value = "demo-user", fallback = UserFallbackService.class)
public interface UserService {
@GetMapping(value = "/member/{id}")
CommonResult detail(@PathVariable Long id);
}

修改配置文件

1
2
3
feign:
sentinel:
enabled: true #在Feign中开启Hystrixs

Gateway

Spring Cloud Gateway 为 SpringBoot 应用提供了API网关支持,具有强大的智能路由与过滤器功能。

Gateway简介

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等。

网关功能

  • 身份认证和权限校验
  • 服务路由和负载均衡
  • 请求限流和服务熔断

image-20230222165747255

Spring Cloud Gateway 具有如下特性:

  • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
  • 集成Hystrix的断路器功能;
  • 集成 Spring Cloud 服务发现功能;
  • 易于编写的 Predicate(断言)和 Filter(过滤器);
  • 请求限流功能;
  • 支持路径重写。

相关概念

  • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;
  • Predicate(断言):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由;
  • Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。

搭建网关模块

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Loadbalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

添加路由配置

Gateway提供了两种不同的方式用于配置路由,一种是通过yml文件来配置,另一种通过Java Bean来配置。

  • 使用配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 9001 # 网关端口

spring:
application:
name: gateway-service # 服务名称
cloud:
nacos:
server-addr: 1.117.34.49:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: demo-user # 自定义路由id
uri: lb://demo-user # 路由目标地址 lb是loadbalance的缩写 后面紧跟服务名称(lb需要引入loadbalance依赖)
predicates: # 路由断言:判断请求是否符合路由规则的条件
- Path=/member/** # 按照路径匹配
  • 使用Java Bean配置
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customizeRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
.route("demo-order",
r -> r.path("/order/**")
.uri("lb://demo-order")
).build();
}
}

image-20230223103617526

image-20230223103659518

路由配置
  • 路由id:路由唯一标识
  • uri:路由目的地,支持lb和http两种
  • predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
  • filters:路由过滤器,处理请求或响应

路由断言工厂

image-20230223122712339

配置文件的断言规则是字符串,这些字符串是被Predicate Factory读取并处理,转变为路由判断的条件。

Path按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理。

PredicateFacrtory的作用是:读取用户定义的断言条件,对于请求做出判断。

Route Predicate的使用

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。 Spring Cloud Gateway包括许多内置的Route Predicate工厂。 所有这些Predicate都与HTTP请求的不同属性匹配。 多个Route Predicate工厂可以进行组合,下面我们来介绍下一些常用的Route Predicate。

image-20230223133936751

After Route Predicate

在指定时间之后的请求会匹配该路由

1
2
predicates:
- After=2019-09-24T16:30:00+08:00[Asia/Shanghai]
Before Route Predicate

在指定时间之前的请求会匹配该路由

1
2
predicates:
- Before=2019-09-24T16:30:00+08:00[Asia/Shanghai]
Between Route Predicate

在指定时间区间内请求会匹配该路由

1
2
predicates:
- Between=2019-09-24T16:30:00+08:00[Asia/Shanghai], 2019-09-25T16:30:00+08:00[Asia/Shanghai]

带有指定Cookie的请求会匹配该路由

1
2
predicates:
- Cookie=username,user # 使用curl工具发送带有cookie为username=user的请求可以匹配该路由
Header Route Predicate

带有指定请求头的请求会匹配该路由

1
2
predicates:
- Header=X-Request-Id, \d+ # 使用curl工具发送带有请求头为X-Request-Id:123的请求可以匹配该路由
Host Route Predicate

带有指定Host的请求会匹配该路由

1
2
predicates:
- Host=**.somehost.com #使用curl工具发送带有请求头为Host:www.macrozheng.com的请求可以匹配该路由
Method Route Predicate

发送指定方法的请求会匹配该路由

1
2
predicates:
- Method=Get # 使用curl工具发送GET请求可以匹配该路由
Path Route Predicate

发送指定路径的请求会匹配该路由

1
2
predicates:
- Path=/user/** # 使用curl工具发送/user/**路径请求可以匹配该路由
Query Route Predicate

带指定查询参数的请求可以匹配该路由

1
2
predicates:
- Query=username # 使用curl工具发送带username=?查询参数的请求可以匹配该路由
RemoteAddr Route Predicate

从指定远程地址发起的请求可以匹配该路由

1
2
predicates:
- RemoteAddr=192.168.1.1/24
Weight Route Predicate

使用权重来路由相应请求,以下表示有80%的请求会被路由到localhost:8201,20%会被路由到localhost:8202。

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: http://localhost:8201
predicates:
- Weight=group1, 8
- id: weight_low
uri: http://localhost:8202
predicates:
- Weight=group1, 2

过滤器工厂

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。

image-20230223142303381

image-20230223150554554

配置方式

  • 局部配置
1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: demo-user # 自定义路由id
uri: lb://demo-user # 路由目标地址 lb是loadbalance的缩写 后面紧跟服务名称
filters: # 过滤器配置
- AddRequestParameter=username, user # 请求添加参数, ','前面为key,后面为value
  • 全局配置
1
2
default-filters: # 全局配置
- AddRequestParameter=username, user # 请求添加参数, ','前面为key,后面为value

AddRequestParameter GatewayFilter

给请求添加参数的过滤器

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: demo-user # 自定义路由id
uri: lb://demo-user # 路由目标地址 lb是loadbalance的缩写 后面紧跟服务名称
filters: # 过滤器配置
- AddRequestParameter=username, user # 请求添加参数, ','前面为key,后面为value
predicates: # 路由断言:判断请求是否符合路由规则的条件
- Path=/member/** # 按照路径匹配

AddRequestHeader GatewayFilter

1
2
filters: # 过滤器配置
- AddRequestParameter=username, user # 请求添加参数, ','前面为key,后面为value

image-20230223150417933

StripPrefix GatewayFilter

对指定数量的路径前缀进行去除的过滤器

1
2
filters:
- StripPrefix=2 # 将以/demo-user/开头的请求路径去除两位,即curl http://localhost:9201/demo-user/a/user/1相当于请求curl http://localhost:9201/user/1

image-20230223144515350

全局过滤器

全局过滤器的作用是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用。区别在于GatewayFilter通过配置定义,处理逻辑是固定的。定义方式是实现GlobalFilter接口。

image-20230223154041790

简单拦截认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Order(0)
@Component
public class AuthorizeFilter implements GlobalFilter {
/**
* 处理当前请求,必要的话将GatewayFilterChain将请求交给下个过滤器处理
* @param exchange 请求上下文,用于获取Request、Response等信息
* @param chain 过滤器链,用于将请求委托给下一个过滤器
* @return 返回标识当前过滤器业务结束
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求ServerHttpRequest对象
ServerHttpRequest request = exchange.getRequest();
// 获取请求参数
MultiValueMap<String, String> queryParams = request.getQueryParams();
// 获取请求参数中的 authorization 参数
String authorization = queryParams.getFirst("authorization");
if ("admin".equals(authorization)) {
// 放行
return chain.filter(exchange);
}
// 拦截
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
}

image-20230223160704698

总结

  • 全局过滤器的作用

    对所有路由生效的过滤器并可以自定义处理逻辑

  • 实现全局过滤器的步骤

    • 继承GlobalFilter接口并实现方法自定义处理逻辑
    • 添加@Order注解或实现Ordered接口说明过滤链顺序

image-20230223160948210

过滤器链

请求进入网关会遇到三种类型的路由器:当前路由的过滤器、DefaultFilter以及GlobalFilter。

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。

image-20230223174035138

过滤器执行顺序

  • 每个过滤器必须指定一个int类型的order值,ordre值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现ordered接口或者添加@Order注解来指定order值
  • 路由过滤器和defaultFilter的order由spring指定,默认是按照声明顺序从1递增
  • 当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobaFilter的顺序执行

image-20230223174222332

总结

  • order值越小,优先级越高
  • 当order值一样时,顺序是defaultFilter最先,然后是局部路由过滤器,最后是全局过滤器

image-20230223174238394

跨域问题

跨域问题:协议、主机以及端口任一不同,浏览器禁止请求的发起者和服务端发生跨域请求。请求被浏览器拦截的问题。

跨越问题处理

image-20230223175700001

网关处理跨域同样采用CORS方案,只需简单的配置即可实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
gateway:
globalcors: # 全局跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求(浏览器询问服务端是否允许跨域的请求)被拦截问题
cors-configurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://1.117.34.49:9001"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期

网关跨域配置核心参数

image-20230223180424312

  • 允许跨域的域名
  • 允许跨域的请求头
  • 允许跨域的请求方式
  • 是否允许使用cookie
  • 跨域的有效期

Seata

Seata是Alibaba开源的一款分布式解决方案,致力于提供高性能和简单易用的分布式事务服务。

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

image

分布式事务问题

事务ACID原则

image-20230327092539294

在微服务架构中由于全局数据一致性没法保证产生的问题就是分布式事务问题。简单来说,在分布式系统下,一次业务操作需要操作多个数据源或需要进行远程调用,每个服务是一个分支事务,要保证所有分支事务最终状态一致性,就会产生分布式事务问题。

  • 单体应用

单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务保证。

img

  • 微服务应用

随着业务需求的变化,单体应用被拆分为微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用独立的数据源,业务操作需要调用三个服务完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题无法保证。

img

微服务下单业务中,下单时会调用订单服务,创建订单并写入数据库。然后订单服务调用账户服务和库存服务:

  • 账户服务负责扣减用户余额
  • 库存服务负责扣减商品库存

image-20230327092727554

理论基础

CAP定理

1998年,加州大学的计算机科学家Eric Brewer提出,分布式系统有三个指标:

  • Consistency(一致性)
  • Availiability(可用性)
  • Partition tolerance(分区容错性)

分布式系统无法同时满足这三个指标,这个结论即CAP定理

image-20230327094538067

Consistency

Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须是一致的。

image-20230327094955301

Availability

Availablility(可用性):用户访问集群中的任意健康节点,必须得到响应,而不是超时或者拒绝

image-20230327095433945

Partition tolerance

Partion(分区):因为网络故障或者其他原因导致分布式系统中的部分节点与其他节点失去连接,形成独立分区。

Tolerance(容错):在集群出现分区时,整个系统要持续对外提供服务。

image-20230327100041330

总结
CAP定理内容
  • 分布式系统节点通过网络连接,一定会出现分区问题

  • 当分区出现时,系统的一致性和可用性无法同时满足

思考:ElasticSearch集群时CP还是AP

ElasticSearch集群出现分区时,故障节点会被剔除集群,数据分配会重新分配到其他节点,保证数据一致。因此是低可用性,高一致性,属于CP

BASE理论

Base理论是对CAP的一种解决思路,包含三个思想:

  • Basically Available(基本可用):分布式系统中出现故障时,允许损失部分可用性,即保证核心可用
  • Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态
  • Eventually Consistent(最终一致性):虽然无法保证一致性,但是在软状态结束后,最终达到数据一致

分布式事务最大的问题是各个子事务的一致性问题,可以借鉴CAP定理和BASE理论:

  • AP模式:各个事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致
  • CP模式:各个子事务执行后相互等待,同时提交,同时回滚,达成强一致,但事务等待过程中,处于弱可用状态

image-20230327101729889

分布式事务模型

解决分布式事务,各个子系统之间必须可以感知到彼此的事务状态,才能保证状态一致,因此需要一个事务协调者来协调每一个事务的参与者(即子系统事务,称为分支事务,有关联的各个分支事务一起称为全局事务)。

image-20230327102852559

总结
BASE理论的三个思想:
  • 基本可用
  • 软状态
  • 最终一致
解决分布式事务的思想和模型:
  • 全局事务:整个分布式事务
  • 分支事务:分布式事务中包含的每个子系统的事务
  • 最终一致思想:各分支事务分别执行并提交,如果有不一致的情况,再采用弥补措施恢复数据,实现最终一致
  • 强一致思想:各分支事务执行完业务不要提交,等待彼此结果后统一提交或回滚

image-20230327103420713

分布式事务解决方案

二阶段提交-2PC

2PC(2 prepare-commit)通过引入一个事务协调器来协调各个本地事务即数据参与者的提交和回滚。

第一阶段-准备阶段:

事务协调器会向每个事务参与者发起一个开启事务的命令,每个事务参与者执行准备操作,然后再向事务协调者回复是否准备完成。此阶段不会提交本地事务,但是资源会被锁住。

第二阶段-提交阶段:

事务协调器收到每个事务参与者的回复后,统计每个参与者的回复,如果每个参与者都回复可以提交,那么事务协调器会发送提交命令,参与者正式提交本地事务,释放所有资源,结束全局事务。如果有一个参与者拒绝提交,那么事务协调器发送回滚命令,所有参与者回滚本地事务,待全部回滚完成,释放资源,取消全局事务。

事务提交流程

img03.png

事务回滚流程

img04.png

问题
  • 同步阻塞会消耗性能
  • 协调者故障问题,一旦协调器发生故障,所有参与者处理资源锁定状态,那么所有参与者处理资源锁定状态,所有参与者都会被阻塞
  • 网络问题导致命令没有收到,导致某些事务执行者没有提交

三阶段提交-3PC

三阶段提交时在二阶段提交基础上的改进版本,引入了超时机制,解决了二阶段单点故障的问题,。3PC分为三个阶段:CanCommit,PreCommit,DoCommit。核心思想是在PreCommited时并不会锁定资源,除非所有参与者都同意了,才开始锁资源。

第一阶段-CanCommit阶段:

协调者向执行者发送CanCommit请求,询问是否可以执行事务提交操作,然后开始等待执行者响应。执行者接收到请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应并进入预备状态,否则返回No。

第二阶段-PreCommit阶段:

协调者根据执行者的反应情况来决定是否可以进入第二阶段事务的PreCommit操作。如果所有的执行者都返回Yes,则协调者向所有执行者发送PreCommit请求,并进入Prepared阶段,执行者接收到请求后,会执行事务操作,并将undo和redo信息记录到事务日志中,如果成功执行,则返回成功响应。

如果所有执行者至少有一个返回No,则协调者向所有执行者发送abort请求,所有的执行者在收到请求超过一段时间没有收到任何请求时,会直接中断事务。

第三阶段-DoCommit阶段:

此阶段是进行真正的事务提交阶段。协调者接收到所有执行者发送的成功响应。阶段从

TTC事务模型

TTC补偿事务:Try、Confirm、Cancel

Try:尝试,即尝试预留资源,锁定资源
Confirm:确认,即执行预留的资源,如果执行失败会重试
Cancel:取消,撤销预留的资源,如果执行失败会重试

img05.png

Seata

Seata是2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案致力于提供高可用和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。

角色

Seata事务管理中有三个重要的角色:

  • TC(Transaction Coordinatoe)-事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚
  • TM(Transaction Manager)-事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务
  • RM(Resource Manager)-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务的状态,并驱动分支事务提交或回滚

image-20230327114354263

Seata提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模型,牺牲了一定的可用性,无业务侵入
  • TTC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入
  • SAGA模式:长事务模式,有业务侵入

image-20230327124043512

部署Seata Server

MySQL创建seata数据库并创建表

image-20230327131720257

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
nacos新增配置文件seataServer.properties

image-20230327140141242

docker部署seata
1
2
3
docker run --name seata \
-p 8091:8091 \
-d seataio/seata-server:1.4.2
拷贝容器配置文件到宿主机
1
docker cp seata:/seata-server/resources/registry.conf /mydata/seata/conf/
编辑配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
registry {
# 注册中心:file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"

nacos {
application = "seata-server"
serverAddr = "1.117.34.49:8848"
group = "DEFAULT_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}

config {
# 配置中心:file、nacos 、apollo、zk、consul、etcd3
type = "nacos"

nacos {
serverAddr = "1.117.34.49:8848"
namespace = ""
group = "DEFAULT_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
在/mydata/seata/目录下创建docker-compose.yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: "3"
services:
seata-server:
image: seataio/seata-server:1.4.2
ports:
- "8091:8091"
environment:
- SEATA_IP=1.117.34.49
- SEATA_PORT=8091
volumes:
- "/mydata/seata/conf/registry.conf:/seata-server/resources/registry.conf"
expose:
- 8091
container_name: seata-server
重新运行seata
1
2
3
docker rm -f seata
cd /mydata/seata/
docker-compose up -d
查看注册服务

image-20230327164756276

image-20230327164809102

Seata的集成

引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- Seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.5.1</version>
</dependency>
编辑配置

image-20230327192457741

1
2
3
4
5
6
7
8
9
10
11
12
13
14
seata:
registry:
type: nacos
nacos:
server-addr: 1.117.34.49:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-server
username: nacos
password: nacos
tx-service-group: fsp_tx_droup # 自定义事务组名称(根据这个获取Seata TC服务的cluster名称) 需要与seata-server对应
service:
vgroup-mapping: # 事务组与TC服务cluster的映射关系
fsp_tx_droup: default
总结

nacos服务名称组成包括:namespace+group+serviceName+cluster

seata客户端获取tc的cluster名称方式:以tx-group-service的值为key到vgroupMapping中查找

image-20230328095254330

Seata实践

XA模式

XA规范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述全局TM与局部RM之间的接口,几乎所有主流的数据库对XA规范提供了支持。

image-20230328102108993

image-20230328102134384

seata的XA模式

image-20230328135642576

image-20230328135541484

总结

优点:

  • 事务强一致性,满足ACID原则
  • 常用数据库都支持,实现简单,并且没有代码侵入

缺点:

  • 一阶段需要锁定数据库资源,等待二阶段结束才释放资源,性能较差
  • 依赖关系性数据库实现事务

image-20230328135841685

实现

image-20230328140402535

1、修改配置文件,开启XA模式

1
2
seata:
data-source-proxy-mode: XA # 开启数据源代理XA模式

2、全局事务的入口方法添加@GlobalTransactional注解

1
2
3
4
5
@Override
@GlobalTransactional
public int insert(OmsOrder omsOrder) {

}
AT模式

AT模式是分阶段提交的事务模型,与XA模式相比弥补了资源锁定周期过长的缺陷

image-20230329112408307

AT模式原理

image-20230329112705673

AT模式和XA模式最大的区别

image-20230329112923405

AT模式的脏写问题

image-20230403174324981

AT模式的写隔离

在提交事务释放DB锁时,获取全局锁,由tc记录当前正在操作行数据的seata事务,该事务持有全局锁,具备执行权。

image-20230403175339314

当非seata事务管理的全局事务操作数据时,会导致不用获取全局锁就修改掉正在被seata全局锁锁住的数据,如果seata的事务回滚,会导致非seata管理的事务丢失更新,seata基于这种情况,会生成两份快照,一份时seata事务执行前的,一份是seata事务生成后的。当seata管理的事务回滚时,会用seata执行后的事务快照和当前数据进行比较,如果发生不一致的情况,即存在其他非seata事务操作数据会进行记录异常,发送警告,人工介入。

image-20230412214127147

AT模式的优缺点

优点:

  • 一阶段直接提交事务,释放数据库资源,性能比较好
  • 利用全局锁实现读写隔离
  • 没有代码侵入,框架自动完成回滚和提交

缺点:

  • 两阶段之间属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但是比XA模式要好很多

image-20230412215719715

实现AT模式

image-20230412220628229

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
DEFAULT CHARSET = utf8mb4;
TCC模式

TTC模式与AT模式相似,每个阶段都是独立事务,不同的是TTC通过人工编码来实现数据恢复。

  • Try资源的检测和预留
  • Confirm完成资源操作业务(如果Try成功Confirm一定成功)
  • Cancel:预留资源的释放(理解为Try的反向操作)
TCC模式原理

image-20230413150920897

TCC工作模型图

image-20230413151229731

总结

image-20230413151713547

TCC模式的每个阶段

  • Try:资源检查和预留
  • Confirm:业务执行和提交
  • Cancel:预留资源的释放

TCC优点:

  • 一阶段直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最好
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务数据库

TCC的缺点:

  • 有代码侵入,需要人为编写Try、Confirm和Cancel接口
  • 软状态,事务最终一致性
  • 需要考虑Confirm和Cancel是失败情况,做好冥等性处理
TTC模式的实现

image-20230413152533116

TCC的空回滚:当某分支事务的try阶段阻塞时,可能会导致全局事务超时而触发二阶段的cancel操作,在未执行try操作时先执行了cancel操作,这时cancel不能回滚,要允许空回滚。

对于已经空回滚的业务,如果继续执行try,就永远不可能confirm或cancel,这就是业务悬挂,应当阻止执行空回滚后的try操作,避免悬挂。

image-20230413154145741

image-20230413155640213

1
2
3
4
5
6
7
8
create table account_freeze_tbl
(
xid varchar(128) not null comment '事务id'
primary key,
member_id bigint not null comment '会员id',
freeze_amount decimal(10, 2) unsigned default 0.00 not null comment '冻结金额',
state tinyint null comment '状态(0->try 1->confirm 2->cancel)'
);
声明TCC接口

image-20230413163012859

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@LocalTCC
public interface PmsProductStockTCCService {
/**
* 第一阶段【尝试】方法
*
* @param productid
* @param deduction
* @return
*/
@TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
int prepare(@BusinessActionContextParameter(paramName = "productId") Long productid,
@BusinessActionContextParameter(paramName = "deduction") Integer deduction);

/**
* 第二阶段【提交】方法
*
* @param businessActionContext
* @return
*/
boolean commit(BusinessActionContext businessActionContext);

/**
* 第二阶段【回滚】方法
*
* @param businessActionContext
* @return
*/
boolean rollback(BusinessActionContext businessActionContext);
}
声明TCC接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@Service
public class PmsProductStockTCCServiceImpl implements PmsProductStockTCCService {
@Resource
private PmsProductService pmsProductService;
@Resource
private StockFreezeTblMapper stockFreezeTblMapper;

@Override
@Transactional(rollbackFor = Exception.class)
public int prepare(Long productid, Integer deduction) {
// 获取分布式事务Xid
String xid = RootContext.getXID();

// 防止业务悬挂同时保证冥等性
StockFreezeTbl stockFreezeTbl = stockFreezeTblMapper.selectById(xid);
if (stockFreezeTbl != null) {
return 0;
}

// 扣减库存
pmsProductService.deduct(productid, deduction);
// 冻结库存
stockFreezeTbl = StockFreezeTbl.builder()
.xid(xid)
.productId(productid)
.freezeStock(deduction)
.state(StockFreezeTbl.state.TRY)
.build();
return stockFreezeTblMapper.insert(stockFreezeTbl);
}

@Override
public boolean commit(BusinessActionContext businessActionContext) {
// 提交事务:删除冻结金额
String xid = RootContext.getXID();
return stockFreezeTblMapper.deleteById(xid) > 0;
}

@Override
@Transactional(rollbackFor = Exception.class)
public boolean rollback(BusinessActionContext businessActionContext) {
// 获取分布式事务Xid
String xid = RootContext.getXID();

// 防空回滚
StockFreezeTbl stockFreezeTbl = stockFreezeTblMapper.selectById(xid);
if (stockFreezeTbl == null) {
stockFreezeTbl = StockFreezeTbl.builder()
.xid(xid)
.productId(Long.valueOf(businessActionContext.getActionContext().get("productId").toString()))
.freezeStock(Integer.valueOf(businessActionContext.getActionContext().get("deduction").toString()))
.state(StockFreezeTbl.state.CANCEL)
.build();
stockFreezeTblMapper.insert(stockFreezeTbl);
return true;
}
// 冥等处理
if (AccountFreezeTbl.State.CANCEL.equals(stockFreezeTbl.getState())) {
return true;
}

// 回滚冻结金额
pmsProductService.refund(stockFreezeTbl.getProductId(), stockFreezeTbl.getFreezeStock());
// 修改冻结金额和事务状态
stockFreezeTbl.setFreezeStock(Integer.valueOf(0))
.setState(StockFreezeTbl.state.CANCEL);
stockFreezeTblMapper.updateById(stockFreezeTbl);
return false;
}
}
SAGA模式

Saga模式是seata提供的长事务解决方案,分为两个阶段

  • 一阶段:直接提交本地事务
  • 二阶段:成功则什么都不做,失败则编写补偿业务进行回滚

image-20230520132625252

优点:
  • 事务参与者可以基于时间驱动实现异步,吞吐能力高
  • 一阶段直接提交事务,没有添加锁,性能好
  • 不用编写TTC模式的try、confirm、rollback的三个阶段的逻辑代码,实现简单

缺点:

  • 软状态持续时间不稳定,时效性差
  • 没有锁,事务没有隔离,会有脏写的情况发生
四种模式的对比

image-20230520133539394

Sentinel

初始Sentinel

雪崩问题

雪崩问题:微服务调用链路中的某个服务故障,引起整个链路中的所有微服务不可用

image-20230526163538390

解决方式

  • 超时处理:设定超时时间,请求超过一定的时间没有响应将会返回错误的信息,不会无休止的等待

image-20230526163732777

  • ​ 舱壁模式(线程隔离):限定每个业务的线程数,避免耗尽整个Tomcat资源

image-20230526164028803

  • 熔断降级:由断路器统计业务执行的异常比例,如果超过阈值则将该业务熔断,拦截该业务的一切的请求,无需等待快速释放

image-20230526164519034

  • 流量控制:限制业务访问的QPS,避免服务流量突增而带来的故障

image-20230526164655333

总结

image-20230526165005655

服务保护技术对比

image-20230526165109396

隔离策略

Hystrix默认使用线程池隔离,而Sentinel只支持信号量隔离

线程池隔离

当一个业务请求进入Tomcat后,Tomcat会为每一个隔离的业务创建一个独立的线程池进行资源隔离,保证线程隔离。但是随着线程数量的增加,CPU会带来额外上下文切换的消耗,这会影响整个服务的性能。

image.png

线程隔离又被称为舱壁隔离,类似于将船体内部空间区隔划分成若干个隔舱,一旦某几个隔舱发生破损进水,水流不会在其间相互流动,相互隔离。

信号量隔离

本质上就是限制每个业务可以使用的线程数量,公用一个线程池,减少线程的创建和上下文的切换,但是线程的隔离性要比线程隔离差一点

熔断降级策略

Hystrix 限制对某个资源调用的异常比例

Sentinel 在限制上提供了更多的策略选择,基于慢调用比例异常比例异常数

限流策略

Hystrix 限流策略有限,Sentinel 可以基于 QPS 支持基于调用关系的限流

流量整形

Sentinel 支持流量的整形,支持慢启动,匀速排队模式

控制台

Sentinel 开箱即用,可配置规则,查看秒级监控,机器发现等

Sentinel

Sentinel简介

image-20230615141521923

Sentinel安装

下载jar包
1
下载地址:https://github.com/alibaba/Sentinel/releases
编辑DockerFile文件
1
2
3
4
5
6
7
8
9
10
11
# 基础镜像
FROM java:8
# 挂载数据卷目录
VOLUME /mydata/cloud/sentinel
# 添加 jar 包
ADD *.jar /sentinel-dashboard.jar
# 定义 时区 参数
ENV TZ=Asia/Shanghai
# 暴露端口
EXPOSE 8080
ENTRYPOINT ["java", "-jar","-Dserver.port=8080","-Dproject.name=sentinel-dashboard","-Dcsp.sentinel.dashboard.server=localhost:8080","-Dsentinel.dashboard.auth.username=sentinel","-Dsentinel.dashboard.auth.password=sentinel","/sentinel-dashboard.jar"]
创建并运行容器
1
2
docker build -t sentinel-server .
docker run --name sentinel -d -p 8080:8080 -p 8719:8719 --restart=always --privileged=true -d sentinel-server
访问控制台

http://{host}:8858/ 账号:sentinel 密码:sentinel

image-20230615142658258

修改配置

可以修改sentinel的默认端口,账号,密码等

1
java -jar sentinel-dashboard-1.8.6.jar -Dserver.port=808

image-20230615143036497

使用Sentinel

order-service 中整合 sentinel,并且配置 sentinel 客户端的地址

引入依赖
1
2
3
4
5
<!-- Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置控制台地址
1
2
3
4
5
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8858 # 配置 sentinel dashboard地址
访问端点,触发监控

默认情况下 sentinel 会监控 SpringMVC 的 每一个端点(Endpoint)

image-20230617143026335

限流规则

入门

簇点链路

簇点链路:应用内的调用链路,链路中被监控每一个接口是一个资源。默认情况下,Sentinel 会监控 SpringMVC 的每一个端点(EndPoint,因此 SpringMVC 的每一个端点(EndPoint)就是调用链路的一个资源

image-20230617144044954

熔断、流控都是针对簇点链路中的资源来设置的,可以点击对应资源后面的按钮来设置规则

image-20230617144029120

设置流控规则

点击资源/product/{id}后面的流控按钮,就可以弹出表单,在表单中添加流控规则

image-20230617165710224

上述含义限制/product/{id}资源的单机QPS为5,即每秒只允许5次请求,超过的请求会被拦截并报错

使用 jmeter 并发测试工具

image-20230617170523546

发现部分请求被 Blocked by Sentinel 即 被 Sentinel 限流

image-20230617170647199

查看 Sentinel实时监控

image-20230617170732671

流控模式

在添加限流规则时,点击高级选项,可以选择三种流控模式

  • 直接(默认):统计当前资源的请求,触发阈值时对当前资源直接限流
  • 关联:统计与当前资源相关的另一个资源,触发阈值时,会对当前资源限流
  • 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

image-20230617171220674

关联模式
  • 关联模式:统计对于当前资源相关的另一个资源,触发阈值时,对当前资源限流
  • 使用场景:比如当用户支付时需要修改订单的状态,如果同时用户需要查询订单,那么查询和更新订单的操作会争抢数据库资源以及数据库锁,产生竞争关系。业务需求是优先支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单的业务进行限流。
  • 实战场景:当/write资源访问量触发阈值时,就会对于/read资源进行限流,避免影响/write资源

image-20230617171844770

添加模拟接口

image-20230617172932578

新增关联模式

给 query 请求新增流控规则

image-20230617173051185

当 update 请求超过 阈值 时 会对于 query 请求进行限流 来保护 update 请求

image-20230617173506445

image-20230617173356719

小结

满足下面条件可以使用关联模式:两个有竞争关系的资源,一个优先级高一个优先级低

image-20230617173754898

链路模式

image-20230617173933530

流控效果

热点参数限流

流量控制

隔离和降级

授权规则

规则持久化