一文弄懂SpringCloud Alibaba服务组件——Sentinel

什么是Sentinel?

作为SpringCloud Alibaba的组件之一,Sentinel被称为是分布式系统的流量防卫兵。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。

Sentinel 功能和设计理念

流量控制

任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。

举个栗子:去吃自助餐时,人太多了就会取号排队,餐厅可容纳客流量达到峰值,就会让用户在外等候,以此达到对客流量的控制,保证餐厅内可以正常运行。

服务的熔断与降级

除了流量控制以外,由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

熔断有三种状态,分别为OPEN、HALF_OPEN、CLOSED。

状态 说明
OPEN 表示熔断开启,拒绝所有请求
HALF_OPEN 探测恢复状态,如果接下来的一个请求顺利通过则结束熔断,否则继续熔断
CLOSED 表示熔断关闭,请求顺利通过

Sentinel的流控使用

Sentinel的使用分为控制台【Dashboard】与 核心库【java客户端】 核心库不依赖 Dashboard,但是结合Dashboard 可以取得最好的效果。

可以从 release 页面 下载最新版本的控制台 jar 包。

启动sentinel-dashboard  我这里就在本地启动了。启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。

java -Dserver.port=8088 -jar sentinel-dashboard-1.8.1.jar

默认用户名和密码都是 sentinel  

此时的控制台是什么都没有的

控制台启动后,客户端需要按照以下步骤接入到控制台。

pom中引入相关依赖,主要Springboot的版本【本文基于2.2.5.RELEASE


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

application.properties

server.port=8081
spring.cloud.sentinel.transport.dashboard=localhost:8088
spring.cloud.sentinel.transport.port=8719

这里的 spring.cloud.sentinel.transport.dashboard 配置控制台的IP和端口

再编写一个控制类

@RestController
public class MemberController {

    //限流规则名称
    private static final String GETMEMBER_KEY = "getMember";

    @RequestMapping("/getMember/{num}")
    @SentinelResource(value = GETMEMBER_KEY,blockHandler = "getMemberBlockHandler")
    public String memberService(@PathVariable("num") Integer num){
        return "会员服务-套餐"+num;
    }

    public String getMemberBlockHandler(Integer num,BlockException e){
        return "当前请求人数过多,请稍后重试"+e.toString();
    }
}

启动项目,测试接口

此时再返回sentinel控制台,刷新

@SentinelResource 注解  注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT)
  • blockHandler / blockHandlerClass
  • fallback /fallbackClass

blockHandler 对应处理 BlockException的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException

blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。

fallback 函数签名和位置要求:

  • 返回值类型必须与原函数返回值类型一致;

  • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。

  • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

接下来通过控制台来测试一下流控规则,  注意这里的资源名要和@SentinelResource注解的value值一致

  • QPS:每秒请求数,当前调用该api的QPS到达阈值的时候进行限流
  • 线程数:当调用该api的线程数到达阈值的时候,进行限流

新增流控规则后,快速访问上文中的接口

如图所示,新增流控规则时还有高级选项,来看看

流控模式: 【默认是直接】

  1. 直接:当api大达到限流条件时,直接限流
  2. 关联:当关联的资源到达阈值,就限流自己
  3. 链路:只记录指定路上的流量,指定资源从入口资源进来的流量,如果达到阈值,就进行限流,api级别的限流

接下来测试一下关联模式【当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。】

修改控制类代码  【此时要做的就是在大批量线程高并发访问/test_ref,看/test是否失效

 @SentinelResource(value = "test1", blockHandler = "exceptionHandler")
    @GetMapping("/test1")
    public String test1()
    {
        System.out.println(Thread.currentThread().getName() + "\t" + "...test1");
        return "-------hello baby,i am test1";
    }


    // Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
    public String exceptionHandler(BlockException ex)
    {

        ex.printStackTrace();
        System.out.println(Thread.currentThread().getName() + "\t" + "...exceptionHandler");
        return String.format("error: test1  is not OK");
    }

    @SentinelResource(value = "test1_ref")
    @GetMapping("/test1_ref")
    public String test1_ref()
    {
        System.out.println(Thread.currentThread().getName() + "\t" + "...test1_related");
        return "-------i am test1_ref";
    }

通过JMeter来模拟高并发测试

启动测试时,访问/test1

链路类型的关联也类似,就不再演示了。

【流控效果】

Warm Up(预热):当流量突然增大的时候,如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。

默认 coldFactor 为 3,即请求 QPS 从 阈值/ 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

【比如我们这里就是 qps达到 10/3  也就是3的时候,开始预热,预热5秒后将阈值提升至10】

修改控制层代码

@SentinelResource(value = "testWarmUp",blockHandler = "exceptionHandlerOfWarmUp")
    @GetMapping("/testWarmUp")
    public String testWarmUp(){
      return "------testWarmUp";
    }

    public String exceptionHandlerOfWarmUp(BlockException e){
        return "预热预热预热";
    }

根据上文提到的预热规则,刚开始刷/testWarmUP,会出现默认错误,预热时间到了后,阈值增加,没超过阈值刷新,请求正常。

如秒杀系统在开启瞬间,会有很多流量上来,很可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。

排队等待:匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。阈值必须设置为QPS。

Sentinel会以固定的间隔时间让请求通过, 访问资源。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过;反之,则马上抛出阻塞异常。

修改控制层

@SentinelResource(value = "testLineUp",blockHandler = "exceptionHandlerOfLineUp")
    @GetMapping("/testLineUp")
    public String testLineUp(){
        return "------testLineUp";
    }

    public String exceptionHandlerOfLineUp(BlockException e){
        return "请排队";
    }

Sentinel的熔断降级使用

文章开头已经解释了什么是熔断降级,接下来在控制台中新增降级规则,具体看一下这些设置项的意思。

资源名:仍然是对应注解@SentinelResource中value的值

熔断策略

慢调用比例      

属性 说明
最大RT 需要设置的阈值,超过该值则为慢应用【单位是毫秒】
比例阈值 慢调用占所有的调用的比率,范围:[0~1]
熔断时长 在这段时间内发生熔断、拒绝所有请求
最小请求数 即允许通过的最小请求数,在该数量内不发生熔断

执行逻辑

熔断(OPEN):请求数大于最小请求数并且慢调用的比率大于比例阈值则发生熔断,熔断时长为用户自定义设置。

探测(HALFOPEN):

  • 当熔断过了定义的熔断时长,状态由熔断(OPEN)变为探测(HALFOPEN)。
  • 如果接下来的一个请求小于最大RT,说明慢调用已经恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)
  • 如果接下来的一个请求大于最大RT,说明慢调用未恢复,继续熔断,熔断时长保持一致

修改控制类代码进行测试,这里为了达到RT条件,我们让线程休眠一段时间。

@RequestMapping("/getMemberThread")
    @SentinelResource(value = "getMemberThread",blockHandler = "getMemberThreadBlockHandler")
    public String getMemberThread(){
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "会员服务-线程套餐";
    }

    public String getMemberThreadBlockHandler(BlockException e){
        return "当前接口已被熔断";
    }

 访问接口,按F5快速刷新

异常比例

通过计算异常比例与设置阈值对比的一种策略。

属性 说明
异常比例阈值 异常比例=发生异常的请求数÷请求总数取值范围:[0~1]
熔断时长 在这段时间内发生熔断、拒绝所有请求
最小请求数 即允许通过的最小请求数,在该数量内不发生熔断

修改控制类

@RequestMapping("/getMemberException")
    @SentinelResource(value = "getMemberException",blockHandler = "getMemberExceptionBlockHandler")
    public String getMemberException(){
        int a = 1/0;
        return "会员服务-线程套餐";
    }

    public String getMemberExceptionBlockHandler(BlockException e){
        return "异常比例处理";
    }

异常数道理是一样的,就不赘述了。

总结一下几种熔断策略

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOWREQUESTRATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs,默认为 1s)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

热点规则

热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限制。

统计参数中的热点参数,并根据配置的限流阀值与模式,对包含热点参数的资源调用进行限流。

比如这里我们对第一个参数设置热点规则

发现只支持QPS模式

  • 参数索引:是 我们controller hot 接口的参数下标
  • 单机阈值:在这里是2,所以一秒钟允许通过2个请求
  • 窗口时长:是在访问该资源QPS超过阈值,触发该规则的有效期,超过后重新统计

修改Controller

@GetMapping("/testHostKey")
    @SentinelResource(value = "testHostKey",blockHandler = "exceptionHostKey")
    public String testHostKey(@RequestParam(name = "name",required = false) String name,
                              @RequestParam(name = "goodId",required = false) Integer goodId){
       return "name:"+name+",goodId:"+goodId;
    }

    public String exceptionHostKey(String name,Integer goodId,BlockException e){
        return "热点规则测试:"+e;
    }

此时会发现只对第一个参数限流

系统规则

系统保护的目标是 在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5

  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。

  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。

  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN

Sentinel配置持久化

大家在做的时候也会发现,修改控制层代码重启项目后,Sentinel-Dashboard中的规则配置也就不存在了,那么如何对Sentinel的配置进行持久化操作?

官网给出的方案有很多

这里我们使用Nacos来做Sentinel配置持久化

pom中添加

<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>           
<dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.8.0</version>
</dependency>

properties中新增配置

#服务名
spring.application.name=sentinel-nacos
#nacos服务地址
spring.cloud.nacos.discovery.server-addr=192.168.56.1:8848
#Sentinel持久化Nacos配置
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=${spring.cloud.nacos.discovery.server-addr}
spring.cloud.sentinel.datasource.ds1.nacos.data-id=nacos-sentinel
spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow

启动项目,访问localhost:8081/getMember/1,请求后再去Sentinel控制台。

Sentinel中添加流控配置

从控制台获取请求参数

在Nacos中新增配置

此时我们重启项目,Sentinel配置仍然存在。

猜你喜欢

转载自blog.csdn.net/QingXu1234/article/details/116301928