SpringCloud总结-2

1 Gateway组件

1.1 Gateway简介

1.1.1 官网

image-20230721090940955

1.1.2 概述

Spring cloud Gateway 是基于Spring5.0 + SpringBoot2.0 + WebFlux 等技术开发的一个全新的API网关项目,目标是替代Netflix Zuul,官测试,Spring Cloud GateWay是Zuul的1.6倍 。

实现路由转发、负载均衡、熔断、鉴权、路径重写、日志监控等,Gateway还内置了限流过滤器,实现了限流的功能。

网关是介于客户端和服务端之间的组件。

1.1.3 架构图

image-20230624161556300

1.2 三大核心概念

1.2.1 Route(路由)

路由是构建网关的基本模块,它由ID,目标服务的URI,一系列的 断言 和 过滤器组成,如果断言为true则匹配该路由。

image-20230721091801547

image-20231011160021999

1.2.2 Predicate(断言)

开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。

image-20230721091835236

1.2.3 Filter(过滤器)

指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

image-20230721091858141

1.3 Gateway入门

下面,我们就演示下网关的基本路由功能。基本步骤如下:

  • 1、在spzx-cloud-parent下创建子模块spzx-cloud-gateway

image-20230721101439700

  • 2、引入如下依赖:
<dependencies>
    <!--网关-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <!--nacos服务发现依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- 负载均衡组件 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-loadbalancer</artifactId>
    </dependency>
</dependencies>
  • 3、编写启动类
// com.atguigu.spzx.cloud.gateway
@SpringBootApplication
public class GatewayApplication {

	public static void main(String[] args) {
		SpringApplication.run(GatewayApplication.class, args);
	}
}
  • 4、在 application.yml 配置文件中编写基础配置和路由规则
server:
  port: 8222
spring:
  application:
    name: spzx-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #利用微服务名进行路由
      routes:
        - id: spzx-cloud-user  # 路由id,可以自定义,只要唯一即可
          uri: lb://spzx-cloud-user  # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates:
            - Path=/*/user/** # 路径匹配
            
        - id: spzx-cloud-order
          uri: lb://spzx-cloud-order
          predicates:
            - Path=/*/order/** # 路径匹配
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

image-20230624163155679

1.4 断言工厂

思考问题:我们在配置文件中只是配置了一个访问路径的规则,怎么就可以实现路由呢?

image-20231011160853172

底层原理:框架底层会自动读取配置文件中的内容,然后通过指定的路由断言工厂(Route Predicate Factories)将其转换成对应的判断条件,然后进行判断。上述案例中使用的是PathRoutePredicateFactory。

image-20231011171526071

Spring Cloud Gateway包含许多内置的Route Predicate Factories,所有这些谓词都匹配HTTP请求的不同属性,多种谓词工厂可以组合,并通过逻辑and 。大致有12个,每一种路由工厂的使用Spring Cloud的官网都给出了具体的示例代码,我们可以参考示例代码进行使用。

https://docs.spring.io/spring-cloud-gateway/docs/4.0.6/reference/html/#gateway-request-predicates-factories

image-20230624163811030

以After Route Predicate Factory路由工厂举例,如下所示:

spring:
  cloud:
    gateway:
      routes:
        - id: spzx-cloud-user
          uri: lb://spzx-cloud-user
          predicates:
            - Path=/*/user/**
            - After=2023-07-21T10:23:06.978038800+08:00[Asia/Shanghai]  # 系统时间在2023-07-21之后才可以进行访问
//获取当前时区时间代码
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);

1.5 过滤器

1.5.1 过滤器简介

在gateway中要实现其他的功能:权限控制、流量监控、统一日志处理等,就需要使用到gateway中所提供的过滤器了。

过滤器,可以对进入网关的请求和微服务返回的响应做处理:

image-20230624164230054

1.5.2 网关中的过滤器

spring gateway提供了31种不同的过滤器。

官网地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

例如:

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除一个响应头
RequestRateLimiter 限制请求的流量
...... ......

在Gateway中提供了三种级别的类型的过滤器:

1、路由过滤器:只针对当前路由有效

2、默认过滤器:针对所有的路由都有效

3、全局过滤器:针对所有的路由都有效,需要自定义。

(1)路由过滤器

需求:给所有进入spzx-cloud-user的请求添加一个请求头:Truth=atguigu

实现:

1、修改gateway服务的application.yml文件,添加路由过滤

spring:
  cloud:
    gateway:
      routes:
        - id: spzx-cloud-user
          uri: lb://spzx-cloud-user
          predicates:
            - Path=/api/user/**
          filters:
            - AddRequestHeader=Truth, atguigu		# 配置路由基本的过滤器,给访问user微服务的所有接口添加Truth请求头

该过滤器写在id=spzx-cloud-user路由下,因此仅仅对访问spzx-cloud-user的请求有效。

2、在spzx-cloud-user的接口方法中读取请求头数据,进行测试

@GetMapping(value = "/findUserByUserId/{userId}")
public User findUserByUserId(@PathVariable(value = "userId") Long userId , @RequestHeader(name = "Truth")String header) {
    log.info("UserController...findUserByUserId方法执行了... ,header: {} " , header);
    return userService.findUserByUserId(userId) ;
}

测试:

http://localhost:8222/api/user/findUserByUserId/1

image-20231011174711269

(2)默认过滤器

如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

spring:
  cloud:
    gateway:
      routes:
        - id: spzx-cloud-user
          uri: lb://spzx-cloud-user
          predicates:
            - Path=/api/user/**
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]
      default-filters:
        - AddRequestHeader=Truth, atguigu at xian

测试:在订单服务的接口上添加.

@RequestHeader(name = "Truth")  String header

(3)全局过滤器

  • 概述

    上述的过滤器是gateway中提供的默认的过滤器,每一个过滤器的功能都是固定的。

    但是如果我们希望拦截请求,做自己的业务逻辑,默认的过滤器就没办法实现。

    此时就需求使用全局过滤器GlobalFilter,全局过滤器的作用也是处理一切进入网关的请求和微服务响应。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。

  • 需求:定义全局过滤器,拦截请求,判断请求参数中是否有username,如果存在则放行,否则拦截,响应401状态码。

    步骤分析:

1、定义一个类实现GlobalFilter和Ordered接口
2、实现filter和getOrder方法
3、将该类纳入到spring容器中
  • 实现代码:

    在网关中创建拦截器。

package com.atguigu.spzx.cloud.gateway.filters;

@Component
public class AuthorizationFilter implements GlobalFilter, Ordered {

    //实现过滤器逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String username = exchange.getRequest().getQueryParams().getFirst("username");
        if(StringUtils.isEmpty(username)){
            //设置响应状态码401
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            //拦截请求
            return exchange.getResponse().setComplete();
        }
        //放行
        return chain.filter(exchange);
    }

    //定义该过滤器的顺序
    @Override
    public int getOrder() {
        return 0;
    }
}

测试:

http://localhost:8222/api/user/findUserByUserId/1

image-20231011181110935

http://localhost:8222/api/user/findUserByUserId/1?username=aaa

image-20231011181150466

1.5.3 过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

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

image-20230624170925571

排序的规则是什么呢?

1、按照order的值进行排序,order的值越小,优先级越高,执行顺序越靠前。

2、路由过滤器 和 默认过滤器 的order值 由spring指定,默认是按照声明顺序从1递增。

3、当过滤器的order值一样时,会按照 globalFilter > defaultFilter > 路由过滤器的顺序执行。

核心源码:

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链。

public Mono<Void> handle(ServerWebExchange exchange) {
    Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
    
    // 获取路由级别的过滤器和默认过滤器的集合
    List<GatewayFilter> gatewayFilters = route.getFilters();
    
    // 获取全局过滤器的集合
    List<GatewayFilter> combined = new ArrayList(this.globalFilters);
    
    // 将路由级别的过滤器和默认过滤器的集合中的元素添加到全局过滤器的集合中
    combined.addAll(gatewayFilters);
    
    // 进行排序
    AnnotationAwareOrderComparator.sort(combined);
    if (logger.isDebugEnabled()) {
        logger.debug("Sorted gatewayFilterFactories: " + combined);
    }

    // 调用过滤器链中的filter方法
    return (new DefaultGatewayFilterChain(combined)).filter(exchange);
}

案例:

image-20231011202531803

image-20231011202719838

image-20231011202849912

2 Nacos配置中心

Nacos除了可以做注册中心,同样可以做配置管理来使用。

2.1 统一配置管理

当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就显得十分的不方便,而且很容易出错。

我们需要一种统一配置管理方案,可以集中管理所有实例的配置。

image-20230624171403235

nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。

2.2 Nacos入门

2.2.1 Nacos中添加配置

在Nacos服务端创建一个配置,如下所示:

image-20230624171530387

然后在弹出的表单中,填写配置信息:

image-20231011205152051

spzx-cloud-user-dev.yml

pattern:
    dateformat: yyyy-MM-dd HH:mm:ss

2.2.2 微服务集成配置中心

微服务需要进行改造,从Nacos配置中心中获取配置信息进行使用。

步骤:

1、在spzx-cloud-user微服务中,引入spring-cloud-starter-alibaba-nacos-config依赖

<!-- nacos作为配置中心时所对应的依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、在spzx-cloud-user中配置 Nacos配置中心地址并引入服务配置

# 配置数据库的连接信息
spring:
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
  config:
    import:
          - nacos:spzx-cloud-user-dev.yml

2.2.3 读取自定义配置

方式1:@Value

通过@Value注解读取自定义配置,如下所示:

@RestController
@RequestMapping(value = "/api/user")
public class UserController {

    @Autowired
    private UserService userService ;

    @Value("${pattern.dateformat}")
    private String pattern ;

    @GetMapping(value = "/findUserByUserId/{userId}")
    public User findUserByUserId(@PathVariable(value = "userId") Long userId) {
        System.out.println(pattern);
        return userService.findUserByUserId(userId) ;
    }

}

测试:http://localhost:10100/api/user/findUserByUserId/1

控制台打印:

image-20231011204623211

方式2:@ConfigurationProperties

也可以通过实体类,配合@ConfigurationProperties注解读取自定义配置,代码如下所示:

1、定义一个实体类,代码如下所示:

@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {

    private String dateformat ;

}
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

2、在启动类上添加@EnableConfigurationProperties注解,如下所示:

@SpringBootApplication
@EnableConfigurationProperties(value = { PatternProperties.class })
public class UserApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class , args) ;
    }

}

3、使用该实体类,代码如下所示:

@RestController
@RequestMapping(value = "/api/user")
public class UserController {

    @Autowired
    private UserService userService ;

    @Value("${pattern.dateformat}")
    private String pattern ;

    @Autowired   // 注入实体类
    private PatternProperties patternProperties ; 

    @GetMapping(value = "/findUserByUserId/{userId}")
    public User findUserByUserId(@PathVariable(value = "userId") Long userId) {
        System.out.println(patternProperties.getDateformat());
        return userService.findUserByUserId(userId) ;
    }

}

2.3 配置热更新

我们最终的目的,是修改Nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新

实现配置的热更新有两种方式:

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

image-20230624200928589

方式二:通过实体类,配合@ConfigurationProperties注解读取配置信息,自动支持热更新

2.4 配置优先级

思考问题:如果在application.yml文件中和Nacos配置中心中都定义了相同的配置内容,那么哪一个配置的优先级较高呢?

优先级顺序:Nacos配置中心的配置(后导入的配置 > 先导入的配置) > application.yml

测试案例:

nacos上的配置:

pattern:
    dateformat: yyyy-MM

application.yml中的配置:

pattern:
  dateformat: yyyy年MM月dd日

结果:打印的是nacos上的配置。

3 Sentinel组件

3.1 初识sentinel

3.1.1 雪崩效应

微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。

如图,如果服务提供者D发生了故障,A服务因为依赖于D服务,因此也会被阻塞。此时,其它不依赖于服务D的业务不受影响。

image-20230624203044831

依赖服务D的业务请求被阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞。导致A服务的线程资源被耗尽,变得不可用。其它依赖A服务的服务慢慢也变得不可用,形成了级联失败,雪崩就发生了。

3.1.2 解决方案

超时处理

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

image-20230624203153340

隔离处理(线程隔离)

隔离处理:将错误隔离在可控的范围之内,不要让其影响到其他的程序的运行。

这种设计思想,来源于船舱的设计,如下图所示:

image-20230624203222353

船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。

与此类似,我们业务系统也可以使用这种思想来防止出现雪崩效应,常见的隔离方式:线程隔离。

20230624203256590-16882902699944

线程隔离有两种方式实现:
1、线程池隔离
	给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果。
2、信号量隔离(Sentinel默认采用)
	信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。

熔断处理

熔断处理:由断路器统计业务执行的异常比例(或异常数),如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。

断路器会统计访问某个服务的请求数量,异常比例如下所示:

image-20230624203334370

请求了三次,两次出现异常,一次成功。当发现访问服务D的请求异常比例过高时,认为服务D有导致雪崩的风险,会拦截访问服务D的一切请求,形成熔断:

image-20230624203409785

触发熔断了以后,服务A就不会在去访问服务D了,立马给用户进行返回,返回的是一种默认值,这种返回就是一种兜底方案。

这种兜底方案也将其称之为降级逻辑。

流量控制

流量控制:限制业务访问的QPS(每秒的请求数),避免服务因流量的突增而故障。

image-20230624203508014

限流是一种预防措施,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。

其他的处理方式是一种补救措施,在部分服务故障时,将故障控制在一定范围,避免雪崩。

3.1.3 sentinel介绍

官网地址:https://sentinelguard.io/zh-cn/

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。

Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

image-20230624203655208

  • Sentinel 的历史:

- 2012 年,Sentinel 诞生,主要功能为入口流量控制。

- 2013-2017 年,Sentinel 在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景。Sentinel 也因此积累了大量的流量归整场景以及生产实践。

- 2018 年,Sentinel 开源,并持续演进。

- 2019 年,Sentinel 朝着多语言扩展的方向不断探索,推出 C++ 原生版本,同时针对 Service Mesh 场景也推出了 Envoy 集群流量控制支持,以解决 Service Mesh 架构下多语言限流的问题。

- 2020 年,推出 Sentinel Go 版本,继续朝着云原生方向演进。

. . .

  • Sentinel 分为两个部分:

- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Spring Cloud 有较好的支持。

- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行。

3.2 sentinel入门

3.2.1 下载sentinel控制台

sentinel管理后台下载地址:https://github.com/alibaba/Sentinel/releases

https://github.com/alibaba/Sentinel/releases/tag/2.0.0-alpha

image-20230624215112184

image-20240111100235521

3.2.2 启动sentinel

  • 将jar包放到任意非中文目录,执行命令:
java -jar sentinel-dashboard-2.0.0-alpha-preview.jar

3.2.3 访问sentinel

访问http://localhost:8080页面,就可以看到sentinel的控制台了:

image-20230624215635555

需要输入账号和密码默认都是:sentinel

登录后,发现一片空白,什么都没有:因为还没有监控任何服务。另外,sentinel是懒加载的,如果服务没有访问,看不到该服务信息。

image-20230624215704921

3.2.4 整合sentinel

我们在spzx-cloud-user中整合sentinel,并连接sentinel的控制台,步骤如下:

1、引入sentinel依赖

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2、配置控制台

修改application.yaml文件,添加下面内容

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080  # 配置sentinel控制台地址

3、访问spzx-cloud-user的任意接口

打开浏览器,访问http://localhost:10100/api/user/findUserByUserId/1,这样才能触发sentinel的监控。

然后再访问sentinel的控制台,查看效果:

image-20230624220303385

3.3 流量控制

雪崩问题虽然有四种方案,但是限流是避免服务因突发的流量而发生故障,是对微服务雪崩问题的预防。

我们先学习这种模式。

3.3.1 快速入门

需求:给 /api/user/findUserByUserId/{userId}这个资源设置流控规则,QPS不能超过 5,然后测试。

步骤:

1、首先在sentinel控制台添加限流规则

image-20230628090407483

2、利用jmeter测试(模拟并发请求)

Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具,用于对软件做压力测试

下载地址:https://archive.apache.org/dist/jmeter/binaries/

课前资料提供了编写好的Jmeter测试样例.

image-20230628115300889

通过如下命令打开jmeter

java -jar ApacheJMeter.jar

导入课前资料提供的测试样例

image-20220320111824238

选择流控入门

image-20220320111955904

线程数改成6,在"流控入门、QPS<5" 上右键“启动”,点击“查看结果树”。 正常情况下,一秒内的6个请求中,5个成功,1个失败(被限流)

image-20231012191953902

3.3.2 流控模式

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

1、直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式

2、关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流

3、链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

如下所示:

image-20230628091856122

快速入门测试的就是直接模式。

(1)直接模式

​ 入门案例就是直接模式。

(2)关联模式

关联模式:对关联资源统计,触发阈值时,对“资源名”指定的资源进行限流

配置方式:

image-20230628092034118

对 关联资源 /api/user/updateUserById 的请求进行统计,当访问流量超过阈值时,就对/api/user/findUserByUserId/{userId}进行限流,避免影响/api/user/updateUserById资源。

使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。
业务需求是优先支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。

案例实现:

1、在UserController新建一个端点:/api/user/updateUserById,无需实现业务。

// 修改用户数据端点
@GetMapping(value = "/updateUserById")
public String updateUserById() {
    return "修改用户数据成功";
}

2、重启服务,访问该接口localhost:10100/api/user/updateUserById,让其产生簇点链路。

image-20230628092515666

3、配置流控规则:

当 updateUserById 资源 的QPS超过5时,对 findUserByUserId 限流。

对哪个端点限流,就点击哪个端点后面的“流控”按钮;我们是对用户查询 findUserByUserId 限流,因此点击它后面的按钮:

image-20230628092751648

4、在Jmeter中进行测试

选择《流控模式-关联》:

image-20220320114459422

可以看到1000个线程,100秒,因此QPS为10(updateUserById的请求),超过了我们设定的阈值:5 。

限流的目标是/api/user/findUserByUserId/1,我们在浏览器访问localhost:10100/api/user/findUserByUserId/1

image-20240827234554708

确实被限流了。

(3)链路模式

链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值,如果超过阈值对从该链路请求进行限流,另一链路不受影响。

例如:两个controller方法,分别调用同一个service方法

1/api/user/save   --> queryUsers

2/api/user/query --> queryUsers

案例实现:

1、在UserService中添加一个queryUsers方法,不用实现业务

public void queryUsers();

public void queryUsers(){
    System.err.println("查询用户");
}

2、在UserController中,添加两个端点,在这两个端点中分别调用UserService中的queryUsers方法

@GetMapping(value = "/save")
public String save() {
    userService.queryUsers();
    System.out.println("保存用户");
    return "订单保存成功" ;
}

@GetMapping(value = "/query")
public String query() {
    userService.queryUsers();
    System.out.println("查询用户");
    return "查询用户成功" ;
}

3、通过 @SentinelResource 标记UserService中的queryUsers方法为一个sentinel监控的资源

(默认情况下,sentinel只监控controller方法)

@SentinelResource("users")
public void queryUsers(){
    System.err.println("查询用户");
}

4、更改application.yml文件中的sentinel配置

spring:
  cloud:
    sentinel:
      web-context-unify: false

5、重启服务,访问两个接口

localhost:10100/api/user/save

localhost:10100/api/user/query

image-20240827234628541

6、添加流控规则

点击users资源后面的流控按钮,在弹出的表单中填写下面信息:

image-20240827234709123

只统计从/api/user/query进入/users的资源,QPS阈值为2,超出则被限流。

7、jmeter测试

选择《流控模式-链路》

image-20240827235601145

可以看到这里200个线程,50秒内发完,QPS为4,超过了我们设定的阈值2。

一个http请求是访问/api/user/save

image-20240827235524528

另一个是访问/api/user/query

image-20230628094713655

运行测试,察看结果树:

访问/api/user/save,没有进行限流

image-20230628094814795

访问/api/user/query,进行限流了

image-20230628094857374

3.3.3 流控效果

在流控的高级选项中,还有一个流控效果选项。

image-20230628095109686

流控效果是指请求达到流控阈值时应该采取的措施,包括三种:

1、快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常,是默认的处理方式

2、warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常,但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值

3、排队等待:让所有的请求按照先后次序进入到一个队列中进行排队,当某一个请求最大的预期等待时间超过了所设定的超时时间时同样是拒绝并抛出异常

(1)快速失败

默认的处理方式。

(2)warm up

阈值一般是一个微服务能承担的最大QPS,但是一个服务刚刚启动时,一切资源尚未初始化(冷启动),如果直接将QPS跑到最大值,可能导致服务瞬间宕机。

warm up也叫预热模式,是应对服务冷启动的一种方案。阈值会动态变化,从一个较小值逐渐增加到最大阈值。

工作特点:请求阈值初始值是 maxThreshold / coldFactor, 持续指定时长(预热时间)后,逐渐提高到maxThreshold值,而coldFactor的默认值是3。

例如,我设置QPS的maxThreshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10。

image-20220320152944101

案例需求:给/api/user/findUserByUserId/{userId}这个资源设置限流,最大QPS为10,利用warm up效果,预热时长为5秒。

1、配置流控规则

image-20230628095505037

2、jmeter测试

选择《流控效果,warm up》

image-20220320153409220

刚刚启动时,大部分请求失败,成功的只有3个,说明QPS被限定在3。

image-20220320153522505

随着时间推移,成功比例越来越高

image-20220320153646510

到sentinel控制台查看实时监控

image-20230628095925921

(3)排队等待

排队等待:让所有的请求按照先后次序进入到一个队列中进行排队,当某一个请求最大的预期等待时间超过了所设定的超时时间时同样是拒绝并抛出异常。

image-20231012202706159

3.3.4 热点参数限流(自定资源名+空指针)

配置介绍

之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。

而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。

例如,一个根据id查询商品的接口:

image-20230628100914491

访问/goods/{id}的请求中,id参数值会有变化,热点参数限流会根据参数值分别统计QPS,统计结果:

image-20240827233423532

当id=1的请求触发阈值被限流时,id值不为1的请求不受影响。

配置方式(点击资源中的热点按钮):

image-20230628101216576

代表的含义是:对hot这个资源的0号参数(第一个参数)做统计,每1秒相同参数值的请求数不能超过2。

这种配置是对查询商品这个接口的所有商品一视同仁,QPS都限定为5。

而在实际开发中,可能部分商品是热点商品,例如秒杀商品;

我们希望这部分商品的QPS限制与其它商品不一样,高一些。

那就需要配置热点参数限流的高级选项了:

image-20230628101331468

案例演示

案例需求:给/api/user/findUserByUserId/{userId}这个资源添加热点参数限流,规则如下:

1、默认的热点参数规则是每1秒请求量不超过2

2、给2这个参数设置例外:每1秒请求量不超过4

3、给3这个参数设置例外:每1秒请求量不超过10

注意事项:热点参数限流对默认的spring mvc资源无效,需要利用@SentinelResource注解标记资源

实现步骤:

1、标记资源

给UserController中的/api/user/findUserByUserId/{userId}资源添加注解:

@SentinelResource("hot")  // 声明资源名称
@GetMapping(value = "/findUserByUserId/{userId}")
public User findUserByUserId(@PathVariable(value = "userId") Long userId) {
    return userService.findUserByUserId(userId) ;
}

重启用户服务,并访问该接口。

2、热点参数限流规则

访问该接口,可以看到我们标记的hot资源出现了

image-20230628101715773

这里不要点击hot后面的按钮,页面有BUG。

点击左侧菜单中热点规则菜单,再点击右侧的“新增热点限流规则”按钮:

image-20230628102031276

3、jmeter测试

选择《热点参数限流 QPS1》

image-20220320162420189

这里发起请求的QPS为5。

包含三个请求,参数分别为:1, 2, 3,运行测试程序,察看结果树。

3.4 熔断处理

3.4.1 工作原理

熔断降级是解决雪崩问题的重要手段。

其思路是由断路器统计服务调用的异常比例、异常数、慢请求比例,如果超出阈值则会熔断该服务,即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

断路器控制熔断和放行是通过状态机来完成的:

image-20230628105744952

状态机包括三个状态:

1、closed:关闭状态,断路器放行所有请求,并开始统计异常比例、异常数、慢请求比例,超过阈值则切换到open状态。

2、open:打开状态,服务调用被熔断,拒绝访问该服务,快速失败,或直接走降级逻辑。Open状态5秒后会进入half-open状态。

3、half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。

① 请求成功:则切换到closed状态

② 请求失败:则切换到open状态

断路器熔断的判断策略有三种:慢调用、异常比例、异常数

3.4.2 慢调用比例

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。sentinel会统计指定时间内,请求数量超过设定的最小请求数时,如果慢调用比例大于设定的阈值,则触发熔断。

例如下述配置:

image-20230628105925241

含义:RT超过50ms的调用是慢调用,统计最近1000ms内的请求,如果请求量超过5次,并且慢调用比例不低于0.4,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。

案例演示

需求:给查询用户接口设置熔断规则,慢调用的RT阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5s

1、修改spzx-cloud-user中的/api/user/findUserByUserId/{userId}这个接口的业务。通过休眠模拟一个延迟时间

@GetMapping(value = "/findUserByUserId/{userId}")
public User findUserByUserId(@PathVariable(value = "userId") Long userId ) {
    try {
        TimeUnit.MILLISECONDS.sleep(60);  // 休眠60ms
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return userService.findUserByUserId(userId) ;
}

2、设置熔断规则

image-20230911140356940

超过50ms的请求都会被认为是慢请求。

3、测试

http://localhost:10100/api/user/findUserByUserId/1

3.4.3 异常比例和异常数

异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

例如,一个异常比例设置:

配置示例 含义
异常比例 image-20220320220813427 统计最近1000ms内的请求,如果请求量超过5次,并且异常比例不低于0.4,则触发熔断。
异常数 image-20220320220955821 统计最近1000ms内的请求,如果请求量超过5次,并且异常比例不低于2次,则触发熔断。

案例演示

需求:给查询用户接口设置熔断规则,统计时间为5秒,最小请求数量为5,异常失败阈值比例为0.4,熔断时长为5s。

步骤:

1、修改spzx-cloud-user中的/api/user/findUserByUserId/{userId}这个接口的业务。手动抛出异常,以触发异常比例的熔断:

@GetMapping(value = "/findUserByUserId/{userId}")
public User findUserByUserId(@PathVariable(value = "userId") Long userId ) {
    if(userId == 2) {
        throw new RuntimeException("手动抛出异常,触发异常比例熔断规则") ;
    }
    return userService.findUserByUserId(userId) ;
}

也就是说,id为2时,就会触发异常。

2、设置熔断规则

image-20230911143051257

3、测试

http://localhost:10100/api/user/findUserByUserId/2

3.5 自定义兜底方案

如果发生熔断或者限流,会返回默认限流信息。

image-20231012205924145

也可以返回自定义兜底数据。

![image-20231012210934976](D:\Program Files\FeiQ\Recv Files\day_7.19_springcloud\课件\20231012210934976)

3.5.1 按资源名限流+后续处理

  • spzx-cloud-user添加统一返回结果类,注意:让资源接口和兜底方案的返回值都是Result
package com.atguigu.spzx.cloud.user.result;

import lombok.Data;

@Data
public class Result<T> {

    //返回码
    private Integer code;

    //返回消息
    private String message;

    //返回数据
    private T data;

    // 私有化构造
    private Result() {}

    // 返回数据
    public static <T> Result<T> build(T body, Integer code, String message) {
        Result<T> result = new Result<>();
        result.setData(body);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

}
  • spzx-cloud-user模块controller中,findUserByUserId方法添加注解@SentinelResource
//hot属性是资源名称, blockHandler属性是兜底方法名称
@SentinelResource(value = "hot",blockHandler = "handleException")
@GetMapping(value = "/findUserByUserId/{userId}")
public Result findUserByUserId(@PathVariable(value = "userId") Long userId) {
    if(userId == 2) {
        throw new RuntimeException("手动抛出异常,触发异常比例熔断规则") ;
    }

    User user = userService.findUserByUserId(userId);
    return Result.build(user, 200,"成功");
}
  • 创建兜底返回方法
//兜底方法参数,要和controller参数保持一致,并且要多加一个参数:BlockException
public Result handleException(Long userId,BlockException exception){
    return Result.build(null,201,"失败了..");
}

重启后,重新配置熔断规则在测试。

![image-20231012210934976](D:\Program Files\FeiQ\Recv Files\day_7.19_springcloud\课件\20231012210934976)

3.5.2 自定义限流处理逻辑

(1)上面兜底方法面临的问题

自定义的处理方法和业务代码耦合在一起,每个业务方法都增加一个兜底的,那代码膨胀加剧。

(2)处理过程

第一步 创建自定义限流处理类,方法必须是public static修饰的

public class CustomerBlockHandler {

    public static Result handleException(Long userId,BlockException exception){
        return Result.build(null,201,"执行了handleException1...");
    }

}

第二步 在controller方法上使用注解进行配置

//blockHandlerClass属性是自定义限流处理类,blockHandler是自定义限流处理类里面的方法名称
@SentinelResource(value = "hot", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException")
@GetMapping(value = "/findUserByUserId/{userId}")
public Result findUserByUserId(@PathVariable(value = "userId") Long userId) {
	if(userId == 2) {
        throw new RuntimeException("手动抛出异常,触发异常比例熔断规则") ;
    }
    User user = userService.findUserByUserId(userId);
    return Result.build(user, 200,"成功");
}

3.6 规则持久化

3.6.1 规则持久化概述

默认情况下sentinel没有对规则进行持久化,当对服务进行重启以后,Sentinel规则将消失,生产环境需要将配置规则进行持久化。

持久化思想:

将限流配置规则持久化进Nacos保存,只要刷新spzx-cloud-user某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,Sentinel上的规则将持续有效。

3.6.2 规则持久化实战

Nacos添加规则配置

在nacos配置中心中添加规则配置 sentinel-rules

image-20230911145913299

规则配置内容如下所示:

[
    {
         "resource": "hot",
         "limitApp": "default",
         "grade": 1,
         "count": 2,
         "strategy": 0,
         "controlBehavior": 0,
         "clusterMode": false 
    }
]

规则说明:

image-20230628113125212

读取nacos规则配置

微服务可以从nacos配置中心读取规则配置信息然后进行使用。

具体步骤如下所示:

1、在spzx-cloud-user微服务中的pom.xml文件中添加如下依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

2、在spzx-cloud-user微服务的application.yml文件添加如下配置

# 配置数据库的连接信息
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
      datasource:
        ds1:
          nacos:
            serverAddr: localhost:8848
            dataId: sentinel-rules
            groupId: DEFAULT_GROUP
            dataType: json
            ruleType: flow

3、重启spzx-cloud-user微服务,访问任意一个接口,此时就可以在sentinel的控制台看到对应的流控规则了

posted @   LilyFlower  阅读(85)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示