《SpringCloud Alibaba 微服务架构》专题(十二)-Spring Cloud Alibaba之Sentinel流控规则

1.引言

我们成功搭建了sentinel流量监控平台,并且实现了对我们微服务应用的监控功能,但是sentinel提供的功能不仅仅只有这些,还有强大的流量控制功能。

2.相关术语

在这里插入图片描述

资源名:唯一名称,默认请求路径;

针对来源Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制);针对调用同一个资源时,Sentinel是能够区分不同调用者,为不同的调用者设置不一样的流控规则;如:app-a设置QPS类型的流控,app-b设置线程数类型的流控。默认default,表示不区分调用者。

阈值类型/单机阈值

  • QPS:每秒钟的请求数量,当调用接口的QPS达到阈值的时候,进行限流;
  • 线程数:当调用接口的线程数达到阈值的时候,进行限流;

是否集群:不需要集群

流控模式:

  • 直接:接口达到限流条件时,直接限流;
  • 关联:当关联的资源达到阈值时,就限流自己;
  • 链路:只记录指定链路上的流量 (指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】;

流控效果

  • 快速失败:直接失败,抛异常;
  • Warm Up:根据codeFactor(冷加载因子,默认为3)的值,即请求 QPS 从阈值 / codeFactor,经过预热时长,逐渐升至设定的QPS阈值;
  • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效;

3.Sentinel流控模式 - QPS直接失败

QPS是什么? 简单理解,QPS就是每秒请求数量

  • Sentinel的流控模式代表的流控的方式,默认【直接】,还有关联,链路。
  • Sentinel的流控效果:默认【快速失败】,还有WarmUp,排队等待。

下面我们通过一个示例说明QPS直接失败,具体配置如下:

【a】点击"簇点链路",查看当前有哪些资源可以进行流量控制
在这里插入图片描述
【b】新增QPS阈值,快速失败规则,具体配置见下图
在这里插入图片描述
添加完成后如下图:
在这里插入图片描述
【c】测试

浏览器访问,http://localhost:8401/sentinel,我们控制1秒内只访问两次该接口:
在这里插入图片描述
可以看到,在没有达到QPS(每秒请求两次)的阈值时,我们的/sentinel资源是正常访问的,下面我们疯狂点击,手动触发1秒内不止发起两个请求:http://localhost:8401/sentinel

在这里插入图片描述
可以看到,/sentinel资源被sentinel限流了,只要不超过QPS的阈值,接口是能够正常被访问的。

当调用该接口的QPS达到阈值的时候,进行限流。

4.Sentinel流控模式 - 线程数直接失败

【a】新增线程数阈值,快速失败规则,具体配置见下图
在这里插入图片描述
【b】改造/sentinel接口,增加模拟网络延时

package com.bruce.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class SentinelController {
    
    

    @GetMapping("/sentinel")
    public String sentinel() {
    
    
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return "hello, sentinel dashboard....";
    }

}

【c】测试

打开两个浏览器窗口,分别访问:http://localhost:8401/sentinel,如果我们按照正常速度访问的话:
在这里插入图片描述
可以看到,是没有什么问题的,但当我们快速切换两个浏览器窗口时,疯狂点击刷新按钮,触发超过线程数阈值:
在这里插入图片描述
可见,如果一秒内访问资源的线程数量超过1个,那么就会触发sentinel的限流。

总结:当线程A过来访问该接口,该请求处理的很慢,还没有返回数据;此时线程B也过来访问该接口,线程B访问接口则会被限流

5.Sentinel流控模式 - 关联

关联:当关联的资源达到阈值的时候,就限流自己,关联模式的核心就是保护关联资源的。举个例子,当与A关联的资源B达到阈值时,就限流A自己。

【a】因为要做资源关联,所以控制层新增如下方法

package com.bruce.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class SentinelController {
    
    

    @GetMapping("/sentinel")
    public String sentinel() {
    
    
        return "hello, sentinel dashboard....";
    }

    @GetMapping("/sentinel2")
    public String sentinel2() {
    
    
        return "hello, sentinel2 dashboard....";
    }

}

【b】配置/sentinel资源关联/sentinel2资源,当/sentinel2资源达到阈值时,限流/sentinel资源。具体配置如下图:
在这里插入图片描述
在这里插入图片描述
【c】使用postman模拟密集访问/sentinel2资源
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【d】测试

当postman还在运行的时候,我们浏览器访问:http://localhost:8401/sentinel
在这里插入图片描述
可以看到,当/sentinel2资源达到阈值后,/sentinel资源就无法访问了,被限流,这就是关联。

分析:因为我们设置/sentinel资源关联/sentinel2资源,并且设置的QPS阈值为2,所以当1秒内访问/sentinel2的次数大于2时,/sentinel将会被限流,注意,并不是限制访问的自身资源(/sentinel2),而是/sentinel。(是不是感觉很霸道,关联资源达到阀值,是本资源接口被限流了)

这种关联模式有什么应用场景呢?

我们举个例子,订单服务中会有2个重要的接口,一个是读取订单信息接口,一个是写入订单信息接口。

在高并发业务场景中,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能。业务中如果我们希望写入订单比较重要,要优先考虑写入订单接口。

那就可以利用关联模式:在关联资源上面设置写入接口,资源名设置读取接口就行了;这样就起到了优先写入,一旦写入请求多,就限制读的请求。

6.Sentinel流控模式 - 链路

链路流控模式指的是,当从某个接口过来的资源达到限流条件时,就开启限流;

链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细

【a】新增业务层接口

package com.bruce.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * @BelongsProject: springcloud-alibaba-nacos
 * @BelongsPackage: com.bruce.service
 * @CreateTime: 2021-02-20 15:14
 * @Description: TODO
 */
@Service
public class SentinelService {
    
    

    private static final Logger logger = LoggerFactory.getLogger(SentinelService.class);

    /**
     * @SentinelResource: 可以理解就是一个资源名
     */
    @SentinelResource("sentinelChain")
    public String sentinelChain() {
    
    
        logger.info("测试Sentinel流控模式 - 链路模式");
        return "Sentinel Mode - Chain";
    }
}

【b】控制层新增两个方法,都是调用上面的业务方法

@Autowired
 private SentinelService sentinelService;

 @GetMapping("/testA")
 public String testA() {
    
    
     log.info("SentinelController>>>>>testA() execute....");
     return sentinelService.sentinelChain();
 }

 @GetMapping("/testB")
 public String testB() {
    
    
     log.info("SentinelController>>>>>testB() execute....");
     return sentinelService.sentinelChain();
 }

【c】Sentitel链路配置

下面我们就可以利用链路模式设置限制哪个入口的流量了,具体配置如下图:
在这里插入图片描述

注意:
从1.6.3 版本开始, Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效;
​1.7.0 版本开始(对应Spring Cloud Alibaba的2.1.1.RELEASE),官方在CommonFilter 引入了WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context;将其配置为 false 即可根据不同的URL 进行链路限流;

上面这段是从百度down下来的,经过测试,SCA换成最新版本2.2.1.RELEASE仍然无效:配置spring.cloud.sentinel.web-context-unify=false无效!!!

推荐做法仍然是关闭官方的CommonFilter实例化,自己手动实例化CommonFilter,设置WEB_CONTEXT_UNIFY=fals

将SpringCloud Alibaba的版本调整2.1.1RELEASE

注意:最新版2.2.1RELEASE的CommonFilter类中,没有WEB_CONTEXT_UNIFY属性,此方法无效!!!

<spring-cloud-alibaba.version>2.1.1.RELEASE</spring-cloud-alibaba.version>

配置文件中关闭sentinel官方的CommonFilter实例化

spring:
    cloud:
        sentinel:
            filter:
                enabled: false

添加配置类,自己构建CommonFilter实例

package com.bruce;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;

/**
 * @BelongsProject: springcloud-alibaba-nacos
 * @BelongsPackage: com.bruce
 * @CreateTime: 2021-02-20 15:27
 * @Description: TODO
 */
@Configuration
public class FilterContextConfig {
    
    

    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
    
    
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        // 入口资源关闭聚合
        registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
        registration.setName("sentinelFilter");
        registration.setOrder(1);
        return registration;
    }
}

测试接口
浏览器分别对/testA和//testB进行频繁刷新访问,发现testB接口没有问题。而testA会有限流现象。
在这里插入图片描述
在这里插入图片描述

7.Sentinel流控效果 - 快速失败

快速失败也是默认的Sentinel流控效果,在前面的示例中已经介绍。

8.Sentinel流控效果 - 预热(Warm Up)

【a】限流、冷启动概述

当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。

这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等。

它的实现是在 Guava 的算法的基础上实现的。然而,和 Guava 的场景不同,Guava 的场景主要用于调节请求的间隔,即 Leaky Bucket,而 Sentinel 则主要用于控制每秒的 QPS,即我们满足每秒通过的 QPS 即可,我们不需要关注每个请求的间隔,换言之,我们更像一个 Token Bucket。

我们用桶里剩余的令牌来量化系统的使用率。假设系统每秒的处理能力为 b,系统每处理一个请求,就从桶中取走一个令牌;每秒这个令牌桶会自动掉落b个令牌。令牌桶越满,则说明系统的利用率越低;当令牌桶里的令牌高于某个阈值之后,我们称之为令牌桶"饱和"。

通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
在这里插入图片描述
QPS阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值。默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

【b】WarmUp配置,具体配置如下图:
在这里插入图片描述
添加完成后如下图所示:
在这里插入图片描述
【c】测试

我们浏览器访问:http://localhost:8401/sentinel2,在访问开始的5秒钟之前,此时QPS还没达到阈值10,我们疯狂点击,因为sentinel还在预热阶段,所以访问结果如下:

在这里插入图片描述
可见,此时接口是会被sentinel限流的。

当过了5秒后,sentinel预热完成,QPS也恢复到阈值10,所以此时我们再次疯狂访问:http://localhost:8401/sentinel2时,接口是不会被sentinel限流的,当然,前提是一秒钟之内访问次数不能超过阈值10。
在这里插入图片描述

分析:因为我们设置的是QPS阈值是10,预热时长是5秒钟,系统初始化刚开始的时候阈值是10 / 3 = 约等于3,然后过了5秒钟后,系统阈值将会恢复升高到我们设置的阈值10。

应用场景:秒杀系统的开启瞬间,会有很多流量上来,很可能会把系统打挂,预热方式就是为了保护系统,可以慢慢的把流量放进来,慢慢的把阈值增长到设定的阈值。

9.entinel流控效果 - 排队等待

【a】概述

排队等待指的是严格控制请求通过的时间间隔,也即是让请求以均匀的速度通过,对应的是漏桶算法。

排队等待的作用如下图所示:
在这里插入图片描述
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

【b】新增一个接口用于测试排队等待

  @GetMapping("/sentinel3")
 public String sentinel3() {
    
    
     logger.info(Thread.currentThread().getName() + ">>>>>>sentinel3");
     return "hello, sentinel dashboard....";
 }

【c】排队等待配置,具体如下图
在这里插入图片描述
【d】借助postman循环访问/snetinel3这个资源
在这里插入图片描述
【e】测试结果

我们看看控制台的日志输出:

2021-02-20 16:07:19.809  INFO 8652 --- [ XNIO-1 task-18] com.bruce.controller.SentinelController  : XNIO-1 task-18>>>>>>sentinel3
2021-02-20 16:07:20.128  INFO 8652 --- [ XNIO-1 task-19] com.bruce.controller.SentinelController  : XNIO-1 task-19>>>>>>sentinel3
2021-02-20 16:07:20.445  INFO 8652 --- [ XNIO-1 task-20] com.bruce.controller.SentinelController  : XNIO-1 task-20>>>>>>sentinel3
2021-02-20 16:07:20.767  INFO 8652 --- [ XNIO-1 task-21] com.bruce.controller.SentinelController  : XNIO-1 task-21>>>>>>sentinel3
2021-02-20 16:07:21.094  INFO 8652 --- [ XNIO-1 task-22] com.bruce.controller.SentinelController  : XNIO-1 task-22>>>>>>sentinel3
2021-02-20 16:07:21.412  INFO 8652 --- [ XNIO-1 task-23] com.bruce.controller.SentinelController  : XNIO-1 task-23>>>>>>sentinel3
2021-02-20 16:07:21.731  INFO 8652 --- [ XNIO-1 task-24] com.bruce.controller.SentinelController  : XNIO-1 task-24>>>>>>sentinel3
2021-02-20 16:07:22.047  INFO 8652 --- [ XNIO-1 task-25] com.bruce.controller.SentinelController  : XNIO-1 task-25>>>>>>sentinel3
2021-02-20 16:07:22.371  INFO 8652 --- [ XNIO-1 task-26] com.bruce.controller.SentinelController  : XNIO-1 task-26>>>>>>sentinel3
2021-02-20 16:07:22.696  INFO 8652 --- [ XNIO-1 task-27] com.bruce.controller.SentinelController  : XNIO-1 task-27>>>>>>sentinel3

我们可以发现请求每隔1秒执行一次,这么多的请求没有被拒绝,而且进入的排队。

分析:因为我们设置的QPS阈值是1,表示一秒内只能有一个请求,但是我们Postman测试0.3秒发起一个请求,很明显超过阈值后,并没有触发sentinel限流,而是一个一个匀速执行。

排队的应用场景是什么呢?

比如有时候系统在某一个时刻会出现大流量,之后流量就恢复稳定,可以采用这种排队模式,大流量来时可以让流量请求先排队,等恢复了在慢慢进行处理

猜你喜欢

转载自blog.csdn.net/BruceLiu_code/article/details/113883227