一、Sentinel介绍
1. 什么是Sentinel
- Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。
分布式系统的流量防卫兵:
- 随着微服务的普及,服务调用的稳定性变得越来越重要。Sentinel以“流量”为切入点,在流量控制、断路、负载保护等多个领域开展工作,保障服务可靠性。
特点:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
- 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
官网文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
2. Sentinel好处
分布式系统面临的问题:复杂的体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败,比如如下的例子中,当我们调用A、E、F、J、K这几个服务的时候如果其中一个服务出现问题会造成什么问题?其实就会出现整体系统效率全部下降,而且严重就会出现服务雪崩的问题!
服务雪崩:
多个微服务之间调用的时候,假设A调用B和C,B和C又调用其他的微服务,这就是所谓的扇出。如果扇出的某个链路上某个微服务调用的响应时间过长或者不可用,微服务A的调用就用占用越来越多的系统资源,从而引起系统崩溃,这也就是服务雪崩。其实就是服务的高可用遭到了破坏。
对于高流量的应用来说,单一的后端依赖可能会导致服务器上的所有资源都在几秒钟内饱和。同时还有可能造成这些应用程序导致服务之间的延迟增加,备份列队,线程和其他的系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系失败,不能取消整个应用程序或系统,所以通常发生了一个模块的某个实例失败后,这时候这个模块依然还会接受流量,然后这个有问题的模块还调用其他的模块,这样就会发生级联故障,或者叫做雪崩。
要解决这种问题的出现我们就需要用到服务降级,而Sentinel就可以保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,提高分布式系统的弹性。
Sentinel的熔断降级通过断路器实现:
断路器:它本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似于熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法出的异常,这样就保证了服务调用方的不会被长时间、不必要的占用,从而避免了故障在分布式系统中蔓延(类似于病毒传染),从而避免了故障在系统中蔓延,乃至崩溃。
好处体现:
对比与其他的产品而言,比如说Hystrix,他不需要我们自己手动搭建监控平台,而且它有一套类似于Nacos的Web界面,可以让我们进行更加细粒度的配置流控、速率、服务熔断、服务降级等
目前主流编程都是 约定>配置>代码,虽然我们的配置都可以写在代码中,但是我们还是要大面积的学习配置和注解的方式,尽量少些代码,这也是Sentinel的理念和初衷。
特征:
生态:
3. 下载和安装
下载地址: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安装成功。
4. 初始化监控
接下来我们可以在以前的项目中增加`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注解标注的资源方法时,当超过阈值就会被限流。
最后这个时候我们再来频繁的访问s3接口,就会出现异常的情况,这也是流量效果快速失败在链路上的体现,是直接抛出异常。
1.5 预热案例讲解
概念:Warm Up方式,即预热/冷启动方式。该方式主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。
预热公式:阈值/coldFactor(默认值为3),经过预热时间后才会达到阈值。
冷启动的过程系统允许通过的QPS曲线如下图:
简单理解:
使用场景:一般秒杀系统中会有这样的流控设置,为了防止秒杀瞬间造成系统崩溃。
案例讲解
默认coldFactor为3,当发起请求即请求QPS从(阈值/3)开始,经过多长预热时长才逐步升至设定的QPS阈值,当前阈值设置为10,预热时长设置为5秒。
最终的效果,系统初始化时阈值/3约等于3,即阈值在此时为3,经过5秒后阈值才慢慢升高到10
首先我们先来设置流控效果:
测试,我们用最简单的方法进行测试,直接在浏览器上手动刷新,然后看Sentinel的实时监控
1.6 排队等待案例讲解
官方文档:https://sentinelguard.io/zh-cn/docs/flow-control.html
概念:匀速排队方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求(削峰填谷)。
匀速器
它的中心思想是,以固定的间隔时间让请求通过。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过。否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);若预期的通过时间超出最大排队时长,则直接拒接这个请求。
Sentinel 匀速排队等待策略是漏桶算法结合虚拟队列等待机制实现的。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
然后我们可以在浏览器中快速的提交访问。通过监控可以看到快速提交的请求都是每一秒处理一个的
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 | 表示熔断关闭,请求顺利通过 |
熔断规则
熔断降级规则包含下面几个重要的属性:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为 s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
2.2 慢调用比例
概念:选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
简单理解:
举例:
案例演示
首先我们先添加一个控制器方法:
@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秒,10秒后进入半开状态,如果有一个请求小于300毫秒。则取消熔断。否则继续熔断。
测试
通过JMeter测试,1秒钟发起10个线程请求/testC,此时就会触发熔断效果,停止测试以后,10秒钟以后恢复正常
2.3 异常比例
概念:异常比例 (ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException
)不生效。
简单理解:
案例讲解
@GetMapping("/s4")
public String sentinelDemo4(Integer id){
if(id != null && id > 1){
throw new RuntimeException("异常比例测试");
}
return "------------testD";
}
设置熔断策略异常比例
上面的熔断配置表示的是:访问/s4资源时。如果在1000毫秒内。请求数量超过5个。并且这些数量中的异常数量的比例超过了0.1.则进入熔断状态5秒,5秒后进入半开状态,如果有一个请求小于300毫秒。则取消熔断。否则继续熔断。
我们通过JMeter来测试,设定HTTP请求地址
当启动JMeter的时候,就会触发熔断,因为此时我们1秒钟发送10个请求超过了最小请求数5,同时超过了阈值,满足了两个条件,当熔断时长过后就会恢复正常。
2.4 异常数
概念:异常数 (ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException
)不生效。
简单理解:
案例演示
@GetMapping("/s4")
public String sentinelDemo4(Integer id){
if(id != null && id > 1){
throw new RuntimeException("异常比例测试");
}
return "------------testD";
}
设置异常数策略,当1秒钟内请求超过5并且异常数大约5个的时候触发熔断
快速请求s4,此时就会触发熔断
3.热点规则
3.1 概念讲解
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效
3.2 案例1
@SentinelResource(value=“xxx”)
那现在我们要完成以上图中的效果,这个时候我们首先要编写代码,在FlowLimitController中编写代码
@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,并且超过阈值,就会出现限流,但是此时的限流效果为报错,显示不友好
@SentinelResource(value=“xxx”,blockHandler=“xxx”)
刚才的演示中,我们明显发现这种限流方法的提示效果非常不友好,所以如果我们需要能够得到友好的提示,我们就需要使用@SentinelResource注解提供的另外一个参数blockHandler,这个参数是可以指定当出现异常时的处理方法,具体操作如下:
@SentinelResource说明:
Sentinel 提供了 @SentinelResource 注解用于定义资源,它有很多的参数,我们这里主要关注两个参数:
- value:代表资源名称,必需项,因为需要通过resource name找到对应的规则,这个是必须配置的
- 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,首先Sentinel页面中修改对应热点规则(在这之前,先演示传递一个参数,否则配置失败)
此时的规则为:如果当前hot1值为除5以外的其他值,都会走普通的阈值规则,但是如果一旦hot1的值为5的时候,将会走参数例外项,此时的阈值为200,我们通过浏览器测试,当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.系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 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 达到阈值即触发系统保护。
QPS案例演示:
最后测试效果不管现在我们访问那个接口只要超过阈值就会被限流
RT:响应时间案例
5.限流逻辑处理
Sentinel 提供了@SentinelResource注解用于定义资源,并提供了AspectJ的扩展用于自定义资源,处理BlockException等。
之前我们用过这个注解,同时了解了它的两个属性:
- value:资源名称,必须项(唯一,不能为空)
- blockHandler:对应处理BlockException的函数名称可选项.blockHandler函数访问需要public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException.blockHandler 函数默认需要和原方法在同一个类中
我们之前利用这个注解完成了热点规则的学习,同时做了一个案例,我们简单复习一下,这个案例的核心思想就是我们传递一个指定参数,然后通过注解@SentinelResource注解标注资源进行限流,当出现限流以后,通过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注解这两种方案的时候,会出现一些问题:
- 没有体现我们自己的业务要求。
- 自定义处理方法和业务代码耦合在一起。
- 每个业务方法都添加一个限流处理方法,代码将会加剧膨胀。
- 无法实现统一全局处理。
解决:@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方法来处理自定义限流逻辑
对应关系图
6.Ribbon 降级
服务熔断:应对微服务雪崩效应的一种链路保护机制,类似保险丝。我们在之前的Nacos的案例基础上来介绍在服务调用中通过Ribbon访问因为网络问题超时等需要做熔断降级的操作
针对调用的方法可能出现的问题。我们需要设置托底的方法
//保证方法签名基本保持一致,但是要添加异常类型参数
public String fallbackHandler(Throwable e){
return "返回托底的數據...";
}
然后测试。当服务提供者不可以访问的情况下,访问后返回的是托底的数据
7.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 "系统繁忙稍后重试。。";
}
然后配置下限流规则
浏览器访问触发限流规则时触发的是blockHandler
如果远程调用的服务不可访问。触发的是fallback
8.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 "返回托底的數據...";
}
9.OpenFeign熔断
实际项目开发中我们更多的是结合OpenFeign来实现服务的调用。
添加Sentinel的依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
添加sentinel的配置
然后我们需要在OpenFeign接口位置定义
@Component
public class NacosProviderServiceImpl implements NacosProviderService{
@Override
public String hello() {
return "托底方法返回的數據....";
}
}
@FeignClient(value = "nacos-provider",fallback = NacosProviderServiceImpl.class)
public interface NacosProviderService {
@GetMapping("/hello")
public String hello();
}
也就是当远程服务调用出现故障会执行 fallback
属性指定的类中重写的方法来处理这个请求从而返回托底的数据,在浏览器中测试如下
@RestController
public class UserContorller {
@Autowired
private NacosProviderService providerService;
@GetMapping("/helloOpenFeign")
public String helloOpenFeign(){
return providerService.hello();
}
}
10.持久化配置
通过前面的案例讲解大家也会发下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不支持该功能。
第一件事情我们首先要引入依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.1</version>
</dependency>
第二件事情我们需要配置YML
# 端口号
server:
port: 8890
# 服务名
spring:
application:
name: order
cloud:
nacos:
discovery:
# nacos注册中心地址
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
datasource: # 配置Sentinel的持久化
nacos:
nacos:
serverAddr: localhost:8848
groupId: DEFAULT_GROUP
dataId: order-sentinel.json
ruleType: flow
profiles:
active: dev
第三步我们需要进入到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 Up,2表示排队等待;
clusterMode:是否集群。
控制器
@RestController
public class OrderController {
@GetMapping("/order/test1")
@SentinelResource(value = "test1")
public String test1() throws InterruptedException {
return "test1 ";
}
}
测试
当我们重启项目以后,我们访问对应接口http://localhost:8890/order/test1,就会在Sentinel界面上看到对应的限流规则: