springCloudAlibaba集成sentinel实战(超详细)

一、Sentinel介绍

1、什么是Sentinel

  • 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。

2、Sentinel 特点

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
  • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

官网文档https://sentinelguard.io/zh-cn/docs/introduction.html

3、Sentinel 基本概念

资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

4、Sentinel好处

分布式系统面临的问题:复杂的体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败,比如如下的例子中,当我们调用A、E、F、J、K这几个服务的时候如果其中一个服务出现问题会造成什么问题?其实就会出现整体系统效率全部下降,而且严重就会出现服务雪崩的问题!
在这里插入图片描述

服务雪崩:

​ 多个微服务之间调用的时候,假设A调用B和C,B和C又调用其他的微服务,这就是所谓的扇出。如果扇出的某个链路上某个微服务调用的响应时间过长或者不可用,微服务A的调用就用占用越来越多的系统资源,从而引起系统崩溃,这也就是服务雪崩。其实就是服务的高可用遭到了破坏。

​ 对于高流量的应用来说,单一的后端依赖可能会导致服务器上的所有资源都在几秒钟内饱和。同时还有可能造成这些应用程序导致服务之间的延迟增加,备份列队,线程和其他的系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系失败,不能取消整个应用程序或系统,所以通常发生了一个模块的某个实例失败后,这时候这个模块依然还会接受流量,然后这个有问题的模块还调用其他的模块,这样就会发生级联故障,或者叫做雪崩

​ 要解决这种问题的出现我们就需要用到服务降级,而Sentinel就可以保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,提高分布式系统的弹性。

Sentinel的熔断降级通过断路器实现:

​ 断路器:它本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似于熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法出的异常,这样就保证了服务调用方的不会被长时间、不必要的占用,从而避免了故障在分布式系统中蔓延(类似于病毒传染),从而避免了故障在系统中蔓延,乃至崩溃。

扫描二维码关注公众号,回复: 17427644 查看本文章

好处体现:

​ 对比与其他的产品而言,比如说Hystrix,他不需要我们自己手动搭建监控平台,而且它有一套类似于Nacos的Web界面,可以让我们进行更加细粒度的配置流控、速率、服务熔断、服务降级等

​ 目前主流编程都是 约定>配置>代码,虽然我们的配置都可以写在代码中,但是我们还是要大面积的学习配置和注解的方式,尽量少些代码,这也是Sentinel的理念和初衷。

特征:
在这里插入图片描述
生态:

在这里插入图片描述

5、Sentinel 是如何工作的

Sentinel 的主要工作机制如下:

  • 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
  • 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
  • Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。

6、下载和安装

下载地址:https://github.com/alibaba/Sentinel/releases

在这里插入图片描述

官方提供的手册:https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/en-us/index.html#_spring_cloud_alibaba_sentinel

Sentinel 分为两个部分

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

启动步骤

  • 前提:jdk1.8环境和8080端口不能被占用

  • 启动命令:java -jar sentinel-dashboard-1.8.2.jar

sentinel默认端口是8080,可启动是自行修改,命令:

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080
-Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

在这里插入图片描述
访问sentinel: http://localhost:8080

在这里插入图片描述

  • 输入默认账号密码:sentinel/sentinel
    在这里插入图片描述
    到这里为止,我们的Sentinel安装成功。

7、初始化监控

接下来我们可以在以前的项目中增加`Sentinel`的监控设置

在这里插入图片描述
然后需要添加对应的依赖

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

在配置yaml文件中添加sentinel的监控设置
在这里插入图片描述
启动服务后访问测试:
在这里插入图片描述

浏览器中访问测试
在这里插入图片描述
然后在sentinel控制器中就可以看到对应的监控信息
在这里插入图片描述
可以看到服务被正常监控到了

二、Sentinel应用

  • 响应时间(Response Time:RT):从客服端发送请求开始到获取到服务器的响应结果的总的时间
  • HPS(Hits Per Second):每秒点击的次数
  • TPS(Transaction Per Second):系统每秒处理的交易数,也叫会话次数
  • QPS(Query Per Second):系统每秒处理查询的次数

1. 流控规则

​ sentinel可以使用的规则很多。我们先来看看流控规则

在这里插入图片描述

弹出框中的内容解释说明:

  • 资源名:唯一名称,默认请求路径
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机阈值:
    • QPS(每秒钟的请求数量):当调用该API的QPS达到阈值的时候,进行限流
    • 线程数:当调用该API的线程数量达到阈值的时候,进行限流
  • 是否集群:当前不需要集群
  • 流控模式:
    • 直接:API达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)(API级别的针对来源)
  • 流控效果:
    • 快速失败:直接失败,抛异常
    • Wam Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFacotor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效

1.1 QPS直接失败案例

添加有两种方式,可以直接在流控规则选项中添加,也可以在簇点链路中添加,一般会采取第二种方式

在这里插入图片描述

这里的意思就是我们现在单机阈值设定为1,代表的是当前这个接口只能被1秒访问一次,超过这个阈值,就会被Sentinel阻塞,现在默认为直接失败,也就是会在前台有一个体现.

测试:快速请求出现限流
在这里插入图片描述

1.2 线程数直接失败案例

刚才我们可以的设置是通过QPS(每秒钟请求的数量)来设置的限流规则,但是我们这里其实还有一个线程数。就是说同一时间只能有一个线程访问该接口。

那我们要演示这种效果,我们就需要让一个线程再进来办理的时候需要2秒

    @GetMapping("/s2")
    public String sentinelExample2() throws InterruptedException {
    
    
        Thread.sleep(2000);
        return "sentinelExample1";
    }

这个时候我们重启项目,访问s2,然后看到已经注册上来了
在这里插入图片描述
设置并发线程数
在这里插入图片描述
QPS和并发线程数规则详解
在这里插入图片描述
然后重新通过访问s2接口,通过两个网页(线程)来快速访问,这个时候我们来看效果
在这里插入图片描述
注意:这个时候虽然效果一致,但是是两种完全不同的规则,一种是QPS,一种是并发线程,这点大家一定要分清!

1.3 关联案例讲解

官方解释:当关联的资源达到阈值时,就限流自己。

​ 通俗解释来说,比如那我们的程序,现在有testA接口和testB接口,当A关联的资源B达到阈值后,就限流自己,也就是B到达阈值,限流A本身。就好像我家孩子在外面打架,我来处理一样。换到程序里面来说比如一个电商系统中,支付系统达到阈值,就限流下订单系统。

在这里插入图片描述

具体演示

​ 当关联资源/s2的qps阈值超时1时,就限流/s1的Rest访问地址,当关联资源到阈值后限制配置好的资源名

    @GetMapping("/s1")
    public String sentinelExample1() {
    
    
        return "sentinelExample1";
    }

    @GetMapping("/s2")
    public String sentinelExample2() throws InterruptedException {
    
    
        Thread.sleep(2000);
        return "sentinelExample1";
    }

给s1添加流控规则

在这里插入图片描述

测试效果,先访问/s2,然后快速访问/s1
在这里插入图片描述

1.4 链路案例讲解

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

​ 比如在一个微服务中,两个接口都调用了同一个Service中的方法,并且该方法用SentinelResource(用于定义资源)注解标注了,然后对该注解标注的资源(方法)进行配置,则可以选择链路模式。

在这里插入图片描述
案例演示
添加配置,让Sentinel 源码中 CommonFilter 中的 WEB_CONTEXT_UNIFY 参数为 false,将其配置为 false 即可根据不同的URL 进行链路限流,如果不配置将不会生效。
在这里插入图片描述

首先我们编写一个Service

@Service
public class TestService {
    
    

    @SentinelResource("common")
    public String common(){
    
    
        System.out.println("common ....");
        return "common";
    }
}

然后更改接口调用这个Service方法

@RequestMapping("/s2")
public String sentinelDemo2(){
    
    
    System.out.println(new Date());
    testService.common();
    return "OK";
}

@RequestMapping("/s3")
public String sentinelDemo3(){
    
    
    System.out.println(new Date());
    testService.common();
    return "OK";
}

接下来配置流控规则:

​ 这里要注意不要对/s2或者/s3进行限流规则的配置,要给用SentinelResource注解标注的资源进行配置限流规则,这里的意思为当我们用入口资源访问被SentinelResource注解标注的资源方法时,当超过阈值就会被限流。

在这里插入图片描述
如下配置,common设置流控,common设置了入口资源是s3,那么common只能每秒处理一个从s3过来的请求。
在这里插入图片描述
最后这个时候我们再来频繁的访问s3接口,就会出现异常的情况,这也是流量效果快速失败在链路上的体现,是直接抛出异常。
在这里插入图片描述

1.5 预热案例讲解

​ 概念:Warm Up方式,即预热/冷启动方式。该方式主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。

大白话来讲,刚开始把 阈值调低,不要让过多的请求访问服务器,导致冲垮服务器,先让服务器一点一点处理,再慢慢加量。经典的例子:一个好久没运动的人,你刚开始让他跑10圈,他可能会累死,但是你给他一个预热时间,比如 第一天跑 2圈,第三天跑 3 圈,第四天跑4圈,以此类推…

​ 预热公式:阈值/coldFactor(默认值为3),经过预热时间后才会达到阈值。

​ 冷启动的过程系统允许通过的QPS曲线如下图:
在这里插入图片描述

使用场景:一般秒杀系统中会有这样的流控设置,为了防止秒杀瞬间造成系统崩溃。

案例讲解

  • 默认coldFactor为3,当发起请求即请求QPS从(阈值/3)开始,经过多长预热时长才逐步升至设定的QPS阈值,当前阈值设置为10,预热时长设置为5秒。
  • 最终的效果,系统初始化时阈值/3约等于3,即阈值在此时为3,经过5秒后阈值才慢慢升高到10

首先我们先来设置流控效果:

在这里插入图片描述
测试,用jmeter压测,差不多每秒5个请求

在这里插入图片描述

验证结果,(jmeter差不多每秒5个请求) 最开始的系统阀值只有3,所以有报错
在这里插入图片描述
后来慢慢到了阀值10,所以一直都成功
在这里插入图片描述

1.6 排队等待案例讲解

​ 官方文档:https://sentinelguard.io/zh-cn/docs/flow-control.html

排队等待这种方式,当请求超过 qps 阈值之后,不会直接抛出异常,而是让多出来的请求先进入到一个队列中进行排队,然后按照阈值允许的时间间隔依次执行,如果队列中所有的请求处理时间加起来刚好等于等待时间,那么,此时新来的请求就会被拒绝.

例如,设置 qps = 5,意味着每 200ms 处理一个队列中的请求. 如果设置 超时时间 为 2000ms,那么意味着队列中如果有 10 个请求,就会把时间占满,此时如果再来新的请求,就会被拒绝.

在这里插入图片描述

​ 这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求(削峰填谷)。

匀速器

​ 它的中心思想是,以固定的间隔时间让请求通过。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过。否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);若预期的通过时间超出最大排队时长,则直接拒接这个请求。

在这里插入图片描述

Sentinel 匀速排队等待策略是漏桶算法结合虚拟队列等待机制实现的。

注意:匀速排队模式暂时不支持 QPS > 1000 的场景。

如下配置,一次性能接收4个请求,每秒处理一个,其余三个进行排队,如果一次大于4说明其余超过了超时时间,会直接拒绝掉。
在这里插入图片描述

通过JMeter进行测试,一秒请求5个请求,可以看到有一个请求拒绝掉了,因为超过了等待时间
在这里插入图片描述

检测sentinel控制台,间隔1s
在这里插入图片描述

2.熔断讲解

熔断是一种保护机制,用于防止系统在高负载或异常情况下继续承受过大的流量压力,从而导致系统崩溃或性能下降。熔断规则可以根据系统的负载情况自动触发熔断,将流量限制在一个可控范围内。

2.1 基本介绍

​ 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。

例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

在这里插入图片描述

​ 现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

熔断的相关的策略

​ Sentinel 提供了一下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

Sentinel在1.8.0版本对熔断降级做了大的调整,可以定义任意时长的熔断时间,引入了半开启恢复支持。下面梳理下相关特性。

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

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

2.2 慢调用比例

结论:

  • 在统计时长内的所有请求,如果请求时间超过xx秒(慢请求)的请求超过一定的比例,且请求数大于最小请求数,将触发熔断,效果是熔断时间内的请求会快速失败。
  • 经过熔断时间后,进入探测恢复状态,当下一个请求仍然是慢请求(请求时间超过xx秒),则再次进入熔断状态,不是则恢复正常状态。

流程图

在这里插入图片描述

简单理解:
在这里插入图片描述
举例:
在这里插入图片描述
详细解释:
在这里插入图片描述
最大RT:

  • 请求最大响应时间,超过这个时间的请求为慢调用。RT是响应时间(reponse time)的意思,单位为毫秒。

比例阈值:

  • 慢调用的比例,超过阈值触发熔断。取值范围0.0-1.0,代表 0% - 100%。(1.8.0 引入)

熔断时长:

  • 熔断开始后,在这个时间内发出的请求会快速失败。单位为秒。

最小请求数:

  • 请求数小于该值时,即使异常比率超出阈值也不会熔断。(1.7.0 引入)

统计时长:

  • 一个时间范围,可以是每1分钟、每3小时。根据这个时间内的请求,来统计总请求和数、计算慢调用比例。(1.8.0 引入)

案例演示:

首先我们先添加一个控制器方法:

@GetMapping("/testC")
public String testC(){
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    return "----testC";
}

设置熔断策略,1QPS>5 并且这些请求的RT>300 并且大于比例阈值触发熔断
在这里插入图片描述

上面的熔断配置表示的是:访问/testC资源时。如果在1000毫秒内,请求数量超过5个,并且这些数量中超过300毫秒的请求数量的比例超过了0.1=10%(如果1s内接收了10个请求,那么就是只要有一个请求是超过300ms的)则进入熔断状态10秒,10秒后进入半开状态,如果10s后在来一个请求小于300毫秒,则取消熔断。否则继续熔断。

测试

通过JMeter测试,1秒钟发起10个线程请求/testC,此时就会触发熔断效果,停止测试以后,10秒钟以后恢复正常
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.3 异常比例

​ 概念:异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

​ 注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简单理解:

在这里插入图片描述

案例讲解

@GetMapping("/s6")
public String s6(Integer id){
    
    
    if(id != null && id > 1){
    
    
        throw new RuntimeException("异常比例测试");
    }
    return "------------s6";
}

设置熔断策略异常比例

在这里插入图片描述

上面的熔断配置表示的是:访问/s4资源时。如果在1000毫秒内。请求数量超过5个。并且这些请求数中的异常数量的比例超过了0.1则进入熔断状态5秒,5秒后进入半开状态,如果5秒后又有请求进来是请求成功的,则取消熔断,恢复正常。如果请求的还是异常再次进入熔断模式。

我们通过JMeter来测试,设定id大于1的HTTP请求地址:

在这里插入图片描述

当启动JMeter的时候,就会触发熔断,因为此时我们1秒钟发送10个请求超过了最小请求数5,同时超过了阈值,满足了两个条件进入熔断。此时网页正常请求id小于1的,可以看到已经进入熔断模式。

在这里插入图片描述
过了5s熔断时间后再次请求则恢复正常。
在这里插入图片描述
如果5s后请求仍然是异常则在进入5s熔断模式。

2.4 异常数

​ 概念:异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

​ 注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。

在这里插入图片描述

简单理解:
在这里插入图片描述

案例演示

    /**
     * 熔断异常数
     */
    @GetMapping("/s7")
    public String s7(Integer id){
    
    
        if(id != null && id > 1){
    
    
            throw new RuntimeException("熔断异常数");
        }
        return "s7";
    }

设置异常数策略,当1秒钟内请求超过5并且异常数大于3个的时候触发熔断

在这里插入图片描述

先用JMeter请求10次,ID>1所以进入/s7进入熔断
在这里插入图片描述

然后正常请求id=1,此时已经触发熔断
在这里插入图片描述
经过5s再次请求,请求正常
在这里插入图片描述

3.热点规则

3.1 概念讲解

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

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效

在这里插入图片描述

3.2 案例1

在这里插入图片描述

案例代码:
热点规则必须需要加此注解:@SentinelResource(value=“xxx”)

@GetMapping("/testHotKey")
@SentinelResource("testHotKey")
public String testHotKey(@RequestParam(value = "hot1",required = false) String hot1,
                         @RequestParam(value = "hot2",required = false)String hot2,
                         @RequestParam(value = "hot13",required = false) String hot3){
    
    
    return "----testHotKey";
}

然后再来配置热点规则。如下配置是每秒只处理一个请求,其余的拒绝掉

在这里插入图片描述

这里要说明一下,参数索引0实际上代表的就是我们设置的hot1参数

在这里插入图片描述

测试:

我们传入参数hot1,1秒访问1次是不会出问题的。

如果快速请求1s内请求超过1次,就会出现限流,但是此时的限流效果为报错,显示不友好。

在这里插入图片描述

刚才的演示中,我们明显发现这种限流方法的提示效果非常不友好,所以如果我们需要能够得到友好的提示,我们就需要使用@SentinelResource注解提供的另外一个参数blockHandler,这个参数是可以指定当出现异常时的处理方法,具体操作如下:

@SentinelResource说明:

Sentinel 提供了 @SentinelResource 注解用于定义资源,它有很多的参数,我们这里主要关注两个参数:

  1. value:代表资源名称,必需项,因为需要通过resource name找到对应的规则,这个是必须配置的
  2. blockHandler:blockHandler 对应处理 BlockException 的方法名称,可选项,访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "handler_HotKey")
public String testHotKey(@RequestParam(value = "hot1",required = false) String hot1,
                         @RequestParam(value = "hot2",required = false)String hot2,
                         @RequestParam(value = "hot13",required = false) String hot3){
    
    
    return "----testHotKey";
}

//处理异常方法,方法签名要和对应的接口方法保持一致
public String handler_HotKey(String hot1, String hot2,String hot3,BlockException exception){
    
    
    return "系统繁忙稍后重试。。";
}

然后热点规则不变,我们最终的到的限流效果如下:

在这里插入图片描述

3.3 案例2

​ 其实参数例外项就是可以达到更加细粒度的控制,比如我们当前的例子中,目前hot1参数在访问时超过阈值就会被限流,但是我们可以通过参数例外项设置hot1具体等于特殊的某个值的时候,触发不同的限流效果。假如hot1的值等于5时,它的阈值可以达到200。

注意:参数例外项中的参数类型仅支持一下7种数据类型

在这里插入图片描述

当前我们需要让hot1的值为5的时候阈值可以达到200(也就是说1秒内请求hot参数值为5的超过200次才进行限流)。

首先Sentinel页面中修改对应热点规则:
在这里插入图片描述

此时的规则为:如果当前hot1值为除5以外的其他值,都会走普通的阈值规则,但是如果一旦hot1的值为5的时候,将会走参数例外项。

我们通过浏览器测试,当hot1的值等于5是只要阈值不超过200就不会出现限流。

在这里插入图片描述

​ 注意:我们现在代码中使用了@SentinelResource注解,此注解处理的是Sentinel控制台配置的异常,通过blockHandler属性设置对应方法来处理和程序本身异常无关。

所以以下程序中如果hot1的值等于6还是会出现RuntimeException。

@SentinelResource(value = "testHotKey",blockHandler = "handler_HotKey")
public String testHotKey(@RequestParam(value = "hot1",required = false) String hot1,
                         @RequestParam(value = "hot2",required = false) String hot2,
                         @RequestParam(value = "hot3",required = false) String hot3){
    
    
    if("6".equals(hot1)){
    
    
        throw new RuntimeException("运行时异常");
    }
    return "-----testHotKey";
}

4.系统规则

在开始之前,先回顾一下Sentinel做系统自适应保护的目的:

  • 保证系统不被拖垮 在系统稳定的前提下
  • 保持系统的吞吐量

​ 系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

​ 系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量,比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

在这里插入图片描述

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

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1(1分钟平均负载) 作为启发指标,进行自适应系统保护。当系统 load1(1分钟平均负载) 超过设定的启发值(阈值),且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps(秒级统计的最大QPS) * minRt(秒级统计的最小响应时间) 估算得出。设定参考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

4.1、QPS案例演示:

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

代码:要携带 entryType = EntryType.IN 代表是系统入口

    /**
     * 系统规则、QPS
     */
    @GetMapping("/s8")
    @SentinelResource(value = "s8",entryType = EntryType.IN)
    public String s8() {
    
    
        System.out.println("s8方法执行了");
        return "----s8";
    }

配置:单个系统的QPS每秒只能处理1个请求
在这里插入图片描述

测试,一秒请求两次,其中一个被拒绝
在这里插入图片描述

4.2、RT响应时间案例

RT:是指从客户端发一个请求开始计时,到客户端接收到从服务器端返回的响应结果结束所经历的时间。

代码:

    /**
     * 系统规则、响应时间案例
     */
    @GetMapping("/s9")
    @SentinelResource(value = "s9",entryType = EntryType.IN)
    public String s9() {
    
    
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        }
        System.out.println("s9方法执行了");
        return "----s9";
    }

配置:
在这里插入图片描述
测试:
在这里插入图片描述

5.授权规则

授权规则可以对请求方来源做判断和控制。

  • 白名单:来源(origin)在白名单内的调用者允许访问
  • 黑名单:来源(origin)在黑名单内的调用者不允许访问

在这里插入图片描述

资源名:接口名

流控应用:是来源者的名单。

代码演示:

java代码:

首先新建一个RequestOriginParser的实现类,Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。

@Component
@Slf4j
public class RequestOriginParserDefinition implements RequestOriginParser {
    
    

    @Override
    public String parseOrigin(HttpServletRequest request) {
    
    
        String serviceName = request.getParameter("serviceName"); //接收请求参数
        if (serviceName == null || "".equals("serviceName")){
    
     //如果参数内容为空
            serviceName = request.getHeader("serviceName");
        }
        log.info("授权信息serviceName--->" + serviceName);
        if (StringUtils.isEmpty(serviceName)){
    
    
            return request.getRemoteAddr(); //根据ip地址处理
        }
        return serviceName;
    }
}
@RequestMapping("/message1")
@SentinelResource(value = "message1", blockHandler = "insertUserBlockHandler")
public String message1() {
    
    
    String message = mumberServiceImpl.message();
    return "message1接口..." + "调用了" + message;
}

对于message1这个接口调用mumberServiceImpl的message()接口,在没有配置授权规则时,是能正常访问的。
在这里插入图片描述

配置授权规则:
在这里插入图片描述

采用postMan工具测试,为该接口参数serviceName设置配置的值admin
在这里插入图片描述
在这里插入图片描述

此处只是采用postman进行了简单的测试,在实际开发中,比如A服务调用B服务的某个接口,是不可能采用postman手动携带参数的,需要在接口中携带参数信息。

6.限流逻辑处理

​ Sentinel 提供了@SentinelResource注解用于定义资源,并提供了AspectJ的扩展用于自定义资源,处理BlockException等。

它的两个属性:

  • value:资源名称,必须项(唯一,不能为空)
  • blockHandler:对应处理BlockException的函数名称可选项.blockHandler函数访问需要public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException.blockHandler 函数默认需要和原方法在同一个类中。

核心点:使用@SentinelResource注解的blockHandler属性,定义出现限流效果时的解决方法。

编写一个新的控制器类型SentinelResourceTestController,使用@SentinelResource注解同时使用blockHandler属性

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

public String handler_resource(BlockException exception){
    
    
    return "系统繁忙";
}

这里要注意一定要给byResource资源添加流控

在这里插入图片描述

测试,测试我们去快速访问http://localhost:8401/byResource,就会出现我们使用@SentinelResource注解中blockHandler属性提供的解决限流异常的方法。

在这里插入图片描述

其实我们在使用@SentinelResource注解这两种方案的时候,会出现一些问题:

  1. 没有体现我们自己的业务要求。
  2. 自定义处理方法和业务代码耦合在一起。
  3. 每个业务方法都添加一个限流处理方法,代码将会加剧膨胀。
  4. 无法实现统一全局处理。

解决:@SentinelResource除了blockHandler可以设置自定义限流处理逻辑方法以外,还提供另外一个属性来设置限流处理逻辑类型blockHandlerClass属性,此属性中设置的方法必需为 static 函数,否则无法解析。

具体逻辑

第一步

创建CustomerBlockHandler类型用于处理自定义限流处理逻辑,首先创建CustomerBlockHandler

/**
 * 此类型用来处理限流自定义逻辑
 */
public class CustomerBlockHandler {
    
    
    public static String handlerException1(BlockException exception){
    
    
        return "handlerException1:系统异常,请稍后重试!";
    }
    public static String handlerException2(BlockException exception){
    
    
        return "handlerException2:网络崩溃了,请稍后重试!";
    }
}

第二步

我们在SentinelResourceTestController类型中添加一个接口方法,同时设置@SentinelResource注解和blockHandlerClass属性对应的类型和这个类型中对应的处理方法

/**
* 此方法用到了自定义限流处理类型CustomerBlockHandler
* 中的handlerException1方法来处理限流逻辑。
*/
@GetMapping("/bycustomer")
@SentinelResource(value = "bycustomer",
                  blockHandlerClass = CustomerBlockHandler.class,
                  blockHandler = "handlerException1")
public String bycustomer(){
    
    
    return "-----bycustomer";
}

第三步

测试:给bycustomer资源添加限流规则,然后来测试在超过限流阈值时处理方法是否为CustomerBlockHandler中handlerException1来进行处理。

在这里插入图片描述

添加流控规则以后,我们再来频繁访问http://localhost:8401/bycustomer,就会看见是CustomerBlockHandler类型的handlerException1方法来处理自定义限流逻辑

在这里插入图片描述

对应关系图

20240124192151192.png&pos_id=img-fd7nwFQw-1712821067649)

7.Ribbon 降级

​ 服务熔断:应对微服务雪崩效应的一种链路保护机制,类似保险丝。我们在之前的Nacos的案例基础上来介绍在服务调用中通过Ribbon访问因为网络问题超时等需要做熔断降级的操作

在这里插入图片描述

针对调用的方法可能出现的问题。我们需要设置托底的方法

在这里插入图片描述

//保证方法签名基本保持一致,但是要添加异常类型参数
public String fallbackHandler(Throwable e){
    
    
    return "返回托底的數據...";
}

然后测试。当服务提供者不可以访问的情况下,访问后返回的是托底的数据

在这里插入图片描述

8.blockHandler和fallback属性

​ 通过上述的内容,我们很清楚的知道了fallback属性的作用,但是大家现在想一个问题,如果我们在使用@SentinelResource属性的时候,同时设置blockHandler属性和fallback属性时,并且同时出现了Sentinel异常和Java异常,这个时候会执行哪个方法那。这个我们通过简单的案例演示下就清楚了

@GetMapping("/helloConsumer")
@SentinelResource(value = "helloConsumer",fallback = "fallbackHandler",blockHandler = "handlerHotKey")
public String helloProvider(){
    
    
    System.out.println("------消费者接口执行了-----");
    // 通过RestTemplate 实现 服务的发现和调用
    return restTemplate.getForObject(serverURL+"/hello",String.class);
}

//保证方法签名基本保持一致,但是要添加异常类型参数
public String fallbackHandler(Throwable e){
    
    
    return "返回托底的數據...";
}

//处理异常方法,方法签名要和对应的接口方法保持一致
public String handlerHotKey(BlockException exception){
    
    
    return "系统繁忙稍后重试。。";
}

然后配置下限流规则

在这里插入图片描述

一秒内快速点击超过3次触发限流规则时触发的是blockHandler,而不是fallback,说明blockHandler是优先执行的。

在这里插入图片描述

点击一下远程调用的服务不可访问。触发的是fallback

在这里插入图片描述

9.exceptionsToIgnore属性

exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

@GetMapping("/helloConsumer")
@SentinelResource(value = "helloConsumer",fallback = "fallbackHandler",
        blockHandler = "handlerHotKey",
        exceptionsToIgnore = {
    
    NullPointerException.class} //被标注的异常将会被 原样抛出
)
public String helloProvider(){
    
    
    System.out.println("------消费者接口执行了-----");
    // 通过RestTemplate 实现 服务的发现和调用
    return restTemplate.getForObject(serverURL+"/hello",String.class);
}

//保证方法签名基本保持一致,但是要添加异常类型参数
public String fallbackHandler(Throwable e){
    
    
    return "返回托底的數據...";
}

10.OpenFeign熔断

实际项目开发中我们更多的是结合OpenFeign来实现服务的调用。

我们需要在OpenFeign接口位置定义。

NacosProviderService远程调用

@FeignClient(value = "nacos-provider",fallback = NacosProviderServiceImpl.class)
public interface NacosProviderService {
    
    

    @GetMapping("/hello")
    public String hello();
}

NacosProviderService实现类:

@Component
public class NacosProviderServiceImpl implements NacosProviderService{
    
    
    @Override
    public String hello() {
    
    
        return "托底方法返回的數據....";
    }
}

也就是当远程服务调用出现故障会执行 fallback属性指定的类中重写的方法来处理这个请求从而返回托底的数据,在浏览器中测试如下

@RestController
public class UserContorller {
    
    

    @Autowired
    private NacosProviderService providerService;

    @GetMapping("/helloOpenFeign")
    public String helloOpenFeign(){
    
    
        return providerService.hello();
    }
}

在这里插入图片描述

11.持久化配置

1、介绍

​ 通过前面的案例讲解大家也会发下Sentinel中的规则定义在重启服务后配置信息就都丢失了。所以实际生产环境中需要配置规则的持久化实现,Sentinel提供多种不同的数据源来持久化规则配置,包括file,redis、nacos、zk。

Sentinel规则持久化到Nacos

将限流规则持久化进Nacos保存,只要刷新8401某个接口地址,Sentinel控制台的流控规则就能感应到,同时只要Nacos里面的配置不删除,针对8401上Sentinel的流控规则就持续有效。

在这里插入图片描述

其实就是实现Sentinel Dashboard与Nacos之间的相互通信,通过Nacos配置文件修改流控规则—拉取—>Sentinel Dashboard界面显示最新的流控规则。

注意:在Nacos控制台上修改流控制,虽然可以同步到Sentinel Dashboard,但是Nacos此时应该作为一个流控规则的持久化平台,所以正常操作过程应该是开发者在Sentinel Dashboard上修改流控规则后同步到Nacos,遗憾的是目前Sentinel Dashboard不支持该功能。

2、规则的种类

Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略。

Sentinel 支持以下几种规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则 和 热点参数规则。

2.1、流量控制规则 (FlowRule)

重要属性:

Field 说明 默认值
resource 资源名称
count 单机阈值
grade 阈值类型,0表示线程数,1表示QPS QPS 模式
limitApp 流控针对的调用来源,若为 default 则不区分调用来源
strategy 流控模式,0表示直接,1表示关联,2表示链路 根据资源本身(直接)
controlBehavior 流控效果,0表示快速失败,1表示WarmUp,2表示排队等待 直接拒绝
clusterMode 是否集群

其它属性:

Field 说明
refResource 关联的资源,流控关联和链路模式使用
warmUpPeriodSec 预热时长,流控效果预热模式使用
maxQueueingTimeMs 超时时间,流控效果排队等待模式使用

同一个资源可以同时有多个限流规则,检查规则时会依次检查。

持久化配置:

  • 流控模式:直接模式
  • 流控效果:快速失败
[
	{
    
    
	  "resource": "nacosDemo",   
	  "limitApp": "default", 
	  "grade": 1, 
	  "count": 2, 
	  "strategy": 0,  
	  "controlBehavior": 0,   
	  "clusterMode": false   
	}
]

对应控制台:
在这里插入图片描述

2.2、熔断降级规则 (DegradeRule)

重要属性:

Field 说明 默认值
resource 资源名称
grade 熔断策略,0表示调用比例/1表示异常比例/2表示异常数策略 慢调用比例
count 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow 熔断时长,单位为 s
minRequestAmount 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) 5
statIntervalMs 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) 1000 ms
slowRatioThreshold 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

持久化配置案例:

[
	{
    
    
	  "resource": "DegradeRuleDemo",   
	  "grade": 0, 
	  "count": 300, 
	  "timeWindow": 3, 
	  "minRequestAmount": 10,
	  "statIntervalMs": 1000,
	  "slowRatioThreshold": 0.5
	}  
]

对应控制台:
在这里插入图片描述

2.3、热点规则 (ParamFlowRule)

重要属性:

属性 说明 默认值
resource 资源名,必填
count 限流阈值,必填
grade 限流模式 QPS 模式
durationInSec 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 1s
controlBehavior 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 快速失败
maxQueueingTimeMs 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 0ms
paramIdx 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置
paramFlowItemList 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型
clusterMode 是否是集群参数流控规则 false
clusterConfig 集群流控相关配置

持久化配置案例:

[
	{
    
    
		"resource": "testHotKey",   
		"count": 2,
    	"durationInSec":1,
    	"paramIdx":0,
    	"paramFlowItemList":[
        	{
    
    
          	"object":100,
          	"count":2,
          	"classType":"java.long.String"
        	}
    	],
    	"clusterMode":"false"
	}  
]

对应控制台:
在这里插入图片描述

2.4、系统规则 (SystemRule)

重要属性:

Field 说明 默认值
highestSystemLoad 资源load1 触发值,用于触发自适应控制阶段 -1 (不生效)
avgRt 所有入口流量的平均响应时间 -1 (不生效)
maxThread 入口流量的最大并发数 -1 (不生效)
qps 所有入口资源的 QPS -1 (不生效)
highestCpuUsage 当前系统的 CPU 使用率(0.0-1.0) -1 (不生效)

持久化配置案例:

{
    
    
  "highestSystemLoad": 3.0,   
  "avgRt": 0.6, 
  "maxThread": 10, 
  "qps": 20, 
  "highestCpuUsage": 0.6
}  

对应控制台:
在这里插入图片描述

3、案例:

第一件事情我们首先要引入依赖:

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

第二件事情我们需要配置YML

server:
  port: 9101
spring:
  application:
    name: nacos-consumer
  cloud:
    sentinel:
      transport:
        dashboard:  localhost:8080  #控制台地址
        port: 8721   #跟控制台交流的端口,随意指定一个未使用的端口即可
      web-context-unify: false
      datasource: # 配置Sentinel的持久化
        flow:  # 这个名字任意起
          nacos:
            serverAddr: localhost:8080
            groupId: DEFAULT_GROUP
            dataId: order-sentinel.json
            dataType: json
            rule-type: flow   #流控模式  ps:flow=流控,degrade=熔断,authority=授权,param-flow=热点,system=系统
        degrade:
          nacos:
            serverAddr: localhost:8080
            groupId: DEFAULT_GROUP
            dataId: order-degrade-sentinel.json
            dataType: json
            rule-type: degrade  #熔断模式
        param-flow:
          nacos:
            serverAddr: localhost:8080
            groupId: DEFAULT_GROUP
            dataId: order-param-flow-sentinel.json
            dataType: json
            rule-type: param-flow #热点
    #nacos配置
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: localhost:8080

feign:
  sentinel:
    enabled: true # 激活SentinelOpenFeign的支持,所以配置yml

management:
  endpoint:
    web:
      exposure:
        include:'*'


第三步我们需要进入到Nacos控制台,添加配置

在这里插入图片描述

具体配置内容:

[   
    {
    
    
        "resource": "test1",
        "limitApp": "default",
        "grade": 1,
        "count": 2,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
---------------具体内容含义-----------------
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up2表示排队等待;
clusterMode:是否集群。

控制器

@RestController
public class OrderController {
    
    
    @GetMapping("/order/test1")
    @SentinelResource(value = "test1")
    public String test1() throws InterruptedException {
    
    
        return "test1 ";
    }
}

测试

当我们重启项目以后,我们访问对应接口http://localhost:8890/order/test1,就会在Sentinel界面上看到对应的限流规则:

在这里插入图片描述

在这里插入图片描述