本文编写自2021年4月13日,当前控制台最新版本为 2021年2月4日发布的1.8.1版本
本文使用版本控制为
spring-boot-dependencies:2.4.4
spring-cloud-dependencies:2020.0.2
spring-cloud-alibaba-dependencies:2.2.1.RELEASE本文使用版本为(受版本控制会自动选择,也不用太关注,这里只是展示一下而已)
SpringCloudAlibaba:2.2.1.RELEASE
(其中包含的Sentinel版本:1.7.1)
Sentinel控制台:v1.8.1
SpringBoot:2.4.4分布式系统的流量防卫兵
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。Sentinel 官网:https://sentinelguard.io/zh-cn/
Sentinel GitHub:https://github.com/alibaba/Sentinel/
Sentinel 中文文档:https://github.com/alibaba/Sentinel/wiki/介绍就和Hystrix差不多,实现服务降级、服务熔断。
本文默认阅读者已经熟练掌握Maven、SpringBoot等相关知识。
一些概念性东西等,源于官方文档,可以直接阅读官方文档。
概述
什么是 Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
特征
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 的主要特性:
Sentinel 的开源生态
Sentinel 功能和设计理念
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:
流量控制有以下几个角度:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程池、系统负载等;
- 控制的效果,例如直接限流、冷启动、排队等。
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
熔断降级
什么是熔断降级
除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。这个问题和 Hystrix 里面描述的问题是一样的。
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
熔断降级设计理念
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。
Sentinel 对这个问题采取了两种手段:
- 通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
- 通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
对比
Sentinel & Hystrix:
下载安装Sentinel控制台
Sentinel这个东西使用的时候只需要导入一个pom依赖就好了,不需要像Nacos一样需要下载一个服务并运行。
但是,Sentinel它有一个控制台,这个东西是单独一个jar工程,需要我们单独下载并运行的。
下载
我们现在是2021年4月13日,最新版本控制台是2021年2月4日更新的 v1.8.1。
两种方式:
- 从最新版本的源码自行构建 Sentinel 控制台:
- 从 release 页面 下载最新版本的控制台 jar 包。
下载源码并编译
- 下载 控制台 工程
- 使用以下命令将代码打包成一个 fat jar:
mvn clean package
下载编译好的jar包
进入https://github.com/alibaba/Sentinel/releases页面获取最新的Sentinel控制台jar包。
(当然你也可以根据需要寻找旧版本下载)
也可以从我的csdn下载:https://download.csdn.net/download/wangguohui0726/16660808
启动
因为是jar包项目,所以无需安装,直接启动即可。
控制台配置的规则(流控规则、熔断规则……)都是临时的。微服务重启就会消失。
注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
使用如下命令启动控制台(最后面的文件名据实修改):
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
其中 -Dserver.port=8080
用于指定 Sentinel 控制台端口为 8080
,也可修改成自己指定的端口。
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel
。可以参考 鉴权模块文档 配置用户名和密码。
注:若您的应用为 Spring Boot 或 Spring Cloud 应用,您可以通过 Spring 配置文件来指定配置,详情请参考 Spring Cloud Alibaba Sentinel 文档。
启动之后的页面(默认用户名和密码都是 sentinel):
http://localhost:8080/#/login
登录之后页面(默认用户名和密码都是 sentinel):
Sentinel控制台采用的是懒加载的机制,哪怕你有别的服务使用了控制台,他这里初始也不显示,需要你访问一次那个服务的接口之后,刷新后才会展示。如果不理解,看到后面用的时候就知道了。
Sentinel的代码案例
SpringCloudAlibaba环境使用(其他使用查看官方文档):
先自己创建一个maven或是Spring Initializr项目,然后再创建我们所需要的子模块,我们要创建好多个子模块,作为服务的提供者和消费者。具体创建过程项目、子模块过程略。。。
我创建的项目名称叫做 SpringCloudAlibabaSentinel
对于依赖管理,我们可以在项目的 pom.xml 中加入如下配置控制SpringCloudAlibaba各组件的版本。
具体获取位置:在我们进入到官方文档后,左侧目录第二项 **2. Dependency Management **里面就有。
页面位置链接:https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/en-us/index.html#_dependency_management
最终完整版父项目的 pom.xml 文件添加以下配置,统一管理子模块的依赖坐标版本(如果父pom不写也行,那么每个子模块就都要写一遍了)。
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
官方demo
https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
创建子模块–>改pom–>写配置–>写代码–>启动
pom.xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
SpringMVC代码
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
@Service
public class TestService {
@SentinelResource(value = "sayHello")
public String sayHello(String name) {
return "Hello, " + name;
}
}
@RestController
public class TestController {
@Autowired
private TestService service;
@GetMapping(value = "/hello/{name}")
public String apiHello(@PathVariable String name) {
return service.sayHello(name);
}
}
SpringWebflux代码
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
@RestController
public class TestController {
@GetMapping("/mono")
public Mono<String> mono() {
return Mono.just("simple string");
}
}
application.yml
server:
port: 9031
spring:
cloud:
sentinel:
transport:
# 这个是微服务和控制台交互的端口,默认8719,冲突时自增寻找不冲突的使用
port: 8719
# 这个填写我们的Sentinel控制台对应地址
dashboard: localhost:8080
application:
name: AliDemo
然后启动,访问对应接口,可以在控制台看到响应监控信息
流量控制
学习此处建议先阅读官方文档 [Sentinel工作主流程](Sentinel 工作主流程),了解其相关概念。
然后阅读 官方文档-流量控制 。
总体介绍
我们在Sentinel的控制台上面可以看到一个叫做流控规则的菜单,在这里面我们可以看到已经创建好的流控规则,右上角可以创建新的流控规则:
也可以在簇点链路菜单里直接对某个接口进行配置:
然后我们对这个流控规则进行一个解释:
- 资源名: 唯一名称,就是我们mvc配置的请求路径
- 针对来源: Sentinel可以针对调用者(微服务名)进行限流,默认default(不区分来源)
- 阈值类型/单机阈值:
- QPS(每秒请求数量):当调用该接口的QPS达到阈值的时候,进行限流
- 线程数:当调用该接口的线程数达到阈值的时候,进行限流
- 是否集群: (勾选和不勾选的配置项会有差别)
- 勾选:是集群
- 未勾选:不集群
- 阈值:能够允许的 QPS/线程数 最大值
- 单机阈值:非集群情况下,单机节点的阈值
- 均摊阈值:集群环境下、单机均摊模式下,每个节点的QPS/线程数阈值
- 总体阈值:集群环境下、总体阈值模式下、整个集群的QPS/线程数阈值
- 流控模式:
- 直接:接口达到限流条件时,直接限流
- 关联:当关联的资源达到限流阈值时,就限流自己
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到峰值,就进行限流)
- 流控效果:
- 快速失败:直接抛异常
- Warm Up:根据coldFactor(冷加载因子,默认3)的值,从阈值/coldFactor,经过预热时长,才达到设置的QPS阈值
- 排队等待:匀速排队,让请求以匀速通过,阈值类型必须设置为QPS,否则无效
- 失败退化: 如果 Token Server 不可用是否退化到单机限流
- 勾选:退化
- 未勾选:不退化
流控模式
流控模式分为三种,直接、关联、链路(上面也说了)。
- 直接:接口达到限流条件时,直接限流
- 关联:当关联的资源达到限流阈值时,就限流自己
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到峰值,就进行限流)
直接
这里只是简单使用并展示Sentinel的流控功能,更深入的使用,我们后面再说,先不要考虑我们怎么配置失败后的指定页面或者啥啥啥的。
以我们已有的服务为例子,对我们的 /hello/{name} 接口进行限流。
左侧菜单->簇点链路->/hello/{name}接口->流控->填写配置信息->新增
阈值类型:QPS,阈值:1:
代表我访问此接口的时候,一秒内连续访问超过1次会被限流。
然后它会自动跳转到"链路规则"菜单,右侧展示我们配置的流控规则。
然后进行验证:
未添加流控规则的时候,我们访问http://localhost:9031/hello/aaa 无论怎么访问,访问多少次都是没问题的。
当我们配置了阈值为1的流控规则的时候,当我们连续访问此接口超过1时,会展示如下内容。
说明我们的限流配置成功了。
之前我们配置的是QPS的,然后我们再尝试一下线程数的
阈值类型:线程数,阈值:1:
代表我访问此接口的时候,一秒内超过一个线程访问会被限流。
将流控规则中的阈值类型修改为线程数并保存。然后我们像之前一样疯狂访问,发现并没有什么效果,无论怎么访问都ok。
这是因为,我们当前配置的是线程数,不是QPS了,我们在一个标签页访问,无论怎么访问都是一个线程,一直没有超出阈值,所以就没有看到被限流的页面了。
我们可以再增加一个标签页,两个标签页都是访问这个接口,然后两个页面来回切换,并快速刷新(刷新就相当于访问了),只要手速够快,我们就会发现有一个标签页看到了被限流的页面。(如果手速不够,可以把配置修改的长一点,比如阈值改为2秒、3秒……)
关联
这个模式是在一个接口达到设定的阈值之后,对另外一个接口进行流控的操作。
为了能够实验,我们本来只有一个接口,我们将 /hello/{name}
接口复制一份,叫做 /hello2/{name}
并重启服务。(别忘记了Sentinel控制台是懒加载的,要想页面展示接口,我们需要都访问一遍)
配置:
这里我们就只展示一个QPS类型的吧,线程数的情况的话,直接模式的看懂了这里线程数的也就会了。
然后我们启动jmeter或者是postman或其他的工具对 /hello2/{name}
接口进行0.3秒一次的循环访问,启动后我们再对/hello/{name}
接口进行访问,就会发现 /hello/{name}
接口被限流了。
我这里用的是jmeter,工具怎么使用我这里就不说了。不过手速快的话,两个标签页,一个hello2,一个hello,疯狂访问hello2,然后访问一次hello也能展示出效果。
链路
链路限流是相当于配置某一条链路访问到这个api才会导致限流,下面展示我们的接口结构图以及控制台配置图
就比如我配置的api是sayHello这个方法(需要加上@SentinelResource
注解),入口是 /hello/123
,这时候,我们通过/hello/123
访问 sayHello
这个方法达到阈值是会进行限流的,但是仅仅只是对 /hello/123
调用 sayHello
这条链路进行限流,/hello2/123
调用 sayHello
是不会被限流的。
但是呢,有个问题,如果按照我的版本来进行学习的话,这个限流是失败的,根本没有上述的限流效果,这是为什么呢?
从 1.6.3 版本开始,Sentinel Web filter 默认收敛所有 URL 的入口 context,因此链路限流不生效。1.7.0 版本开始(对应 SCA 2.1.1.RELEASE),我们在 CommonFilter 引入了 WEB_CONTEXT_UNIFY
这个 init parameter,用于控制是否收敛 context(入口资源关闭是否聚合)。将其配置为 false
即可根据不同的 URL 进行链路限流。
我们需要进行以下配置(记得重启):
添加一个pom依赖并添加一个配置类,然后重新配置限流就可以了:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
</dependency>
@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;
}
}
我们再看一下配置前和配置后的控制台差别:
配置前:
配置后:
我们可以明显看出来,配置了入口资源关闭聚合之后,sayHello不会再合并了,每一个接口都有一份了。
再有,也可以观察到这样的一个现象:你调用 /hello/123
的时候,只有 /hello/123
下面的 sayHello
的分钟通过会增加1,而 /hello2/123
下面的 sayHello
是不会增加的。
流控效果
流控模式分为三种,快速失败、Warm Up、排队等待(上面也说了)。
- 快速失败:直接抛异常
- Warm Up:根据coldFactor(冷加载因子,默认3)的值,从阈值/coldFactor,经过预热时长,才达到设置的QPS阈值
- 排队等待:匀速排队,让请求以匀速通过,阈值类型必须设置为QPS,否则无效
快速失败
默认的流控效果。
快速失败就是在我们达到阈值的时候直接抛异常。我们之前的代码、案例都是使用的快速失败模式。
Warm Up
官方描述:https://github.com/alibaba/Sentinel/wiki/流量控制#warm-up
我们先对 /hello/{name}
接口进行一个WarmUp效果的限流配置。
此配置的含义:单机阈值6,预热时长3
在我们刚开始ide时候它会根据 阈值/coldFactor 规则进行流控,因为coldFactor默认是3,也就是说刚刚开始的时候,阈值是6/3=2,直到预热时长(3秒)时间达到了之后,才会将阈值提升到6,其过程是逐步提升的。
配置之后我们可以开始访问接口 /hello/{name}
进行测试。刚开始的3秒内,我们如果一秒内访问超过2次,就会看到限流现象,在3秒之后,我们一秒内访问超过6次才会产生限流现象(一秒6次用手动刷新挺费劲,所以一般看到的都是无论我怎么访问都没有限流,因为手动没有达到一秒6次访问啊,如果非要看到限流效果,找个工具比如jmeter进行访问)。
匀速排队
官方描述:https://github.com/alibaba/Sentinel/wiki/流量控制#匀速排队
简单描述下就是:无论你来多少个请求,我每秒只处理阈值量的请求,其余多的请求我也不报错,都在后面拍着,等前面的请求处理完之后再进行处理。
匀速排队只允许阈值类型是QPS类型,不支持线程数类型,利用的是漏桶算法。
为了展示排队效果,我们将代码中添加一句打印语句:
控制台限流配置:
然后我们开始进行测试,疯狂访问 http://localhost:9031/hello/12323 然后观察控制台内容。我们会发现,虽然我们是连着疯狂请求的,但是控制台是每隔一秒打印一句,说明了他是每一秒执行一次请求。
那下面的超时时间是干嘛的?
我们可以想象成所有的请求都是放在了一个队列里等待执行,如果我队列里的任务超出了设置的等待时间,那么这个请求就会超时报错(超时时间是以毫秒为单位)。
这个就不演示了,我懒了……
熔断降级
官方文档:https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
Sentinel的服务熔断、降级和Hystrix的熔断、降级是很像的。
旧版本的Sentinel的熔断是没有半开状态的(Hystrix有),但是!新版1.8的有!!!
根据新建降级规则页面内容,我们可以了解到,Sentinel的熔断策略一共有三个:慢调用比例(也就是旧版本的RT)、异常比例、异常数。
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 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
慢比例调用
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(
statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
所以我们能够看出,熔断规则为:请求响应时间大于最大RT 并且 单位统计时长内请求数目大于最小请求数
在这里我们可以看到,慢比例调用有5个配置值
- 最大RT: 单位毫秒,最大的响应时间,超过该值则记为慢调用
- 比例阈值: 0到1,就是达到多少之后会进行熔断
- 熔断时长: 单位秒,经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
- 最小请求数: 触发熔断的最小请求数目,若当前统计窗口内的请求数小于此值,即使达到熔断条件规则也不会触发
- 统计时长: 单位毫秒,就是判断熔断时的单位时间
总体来说就是:在统计时长时间内,某个接口的请求数大于最小请求数、请求响应时间大于最大RT的请求比例大于比例阈值的时候会进行熔断。熔断的时间为熔断时长,在熔断时长达到后,会尝试进入半开状态,如果下一个请求时长大于RT继续熔断,小于则恢复正常。
然后我们进行一下配置并尝试。
此配置表示:一秒内 /hello/{name}
接口的请求数超过2、并且有一半的请求超过了800毫秒就进行熔断,熔断时间为2秒。
然后为了保证接口请求时长超过800毫秒,我们改造下代码,让它sleep1秒。
然后我们疯狂访问会发现确实被熔断了
在熔断期间2秒内我们再次请求发现还是被熔断的,然后我们等待2秒,再次请求会发现熔断结束了。
但是吧,我发现他这里有个Bug,就是我新建的时候我的比例阈值是0.5,可是当我编辑的时候,它回显的是1。
还有统计时长也是,无论我设置的是多少,编辑的时候都是1000。
异常比例
当单位统计时长(
statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。
让我们看一下这个配置页面:
在统计时长内,如果接口收到的请求大于最小请求数,并且出错的请求比例大于比例阈值,那么就会进行熔断,熔断时间为熔断时长。
我们来演示一下这个效果:
将 Hello/{name}
接口进行修改,让他每次都报错,然后重启:
比如我设置比例阈值为1,最小请求数为2,熔断时间3秒,统计时间1秒。
那么就会在1秒内接收到两次以上请求,并且请求都报错的时候进行熔断,熔断3秒后恢复。
之后我们访问 http://localhost:9031/hello/12323 ,结果如下:
- 一秒内访问次数小于等于2次的的时候,页面只会报错,并没有进行熔断。
- 一秒内访问次数大于2次的时候,页面开始从报错页面转变成了熔断页面。
- 当熔断触发的时候,我继续访问还是会一直熔断(展示熔断页面),当我停止访问3秒后,再次进行访问,发现熔断结束了(展示报错页面)。
异常数
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
其实异常数和异常比例很像,只不过是从比例转变成了具体的数量了而已。
如果直接一看就懂了就不需要看这一段的内容了,直接跳过就行了,我写只是为了文档的完整。
熔断效果展示:
- 删除之前的熔断规则,保证环境的干净,不会被其他规则影响
- 使用异常比例熔断规则的代码就可以。
- 配置熔断规则
- 验证
- 在一秒内如果访问次数超过2次,异常次数超过1次,进行熔断,熔断时间3秒。
- 经过3秒后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
热点参数限流
根据前面的讲述,限流规则肯定已经比较熟悉了。然而,在Sentinel中,还有一种单拿出来的一种限流,也就是此部分的热点参数限流。
相比之下,热点参数限流就是在普通的限流功能上,增加了对于指定参数的限流功能,也是一个比较实用的限流功能。
描述一个功能场景:比如我们有一个查询商品接口,这个接口我们会经常访问,访问量特别大,我们需要进行限流(比如一秒内只能查询100次)。但是里面有几个特殊的商品,我们需要查询它的时候能够有一些特例,比如id为6(也就是指定对应特例参数的值)的数据可以一秒钟内查询100000次,这样的时候我们就可以用到热点参数限流功能。如果觉得这么描述还是不容易理解,我又举了个例子:
比如我有一个接口 /goods/findById?id=
经常会用到,并发量特别高,我需要对这个接口进行一个限流处理,但是id为6的商品是一个我收了广告费的商品,那我就需要在查询这个商品的时候提高查询时限流的阈值,这时候我就可以使用热点参数限流,对 /goods/findById
接口进行限流100,对id为6的特例限流100000。
我觉得这回应该是能理解了。
然后我们来看一下控制台配置页面:
然后我们解释一下配置信息的各个参数
基础配置:
- 资源名:@SentinelResource 注解配置的值
- 参数索引: 第几个索引,从0开始,以java代码中的参数为准,而不是url中的。它只关注你指定索引的参数,其他索引位置的参数他不管。
- 单机阈值: 单位时间内达到的限流的访问量最大值
- 统计窗口时长: 统计多久的时间内的请求量
高级选项:
- 参数类型: 就是参数的类型。8个基本类型除去short、boolean再加上String,一共七种。
- 参数值: 恩,就是这个参数的值。在值是这个值得时候,使用这个例外项的配置内容。
- 限流阈值: 在请求的参数值符合配置的例外项的时候,使用此例外项对应的阈值。
验证:
首先我们把代码恢复到这样
然后启动项目,访问接口(因为控制台懒加载),进入控制台,配置热点参数限流。
然后我们访问该接口,一秒内访问一次的时候没问题,一秒钟内访问超过两次的时候它就会进行限流。
然后我们添加一个参数例外项,值为abc的限流阈值修改为100(只影响值为100的,其他的值还是使用上面的1)。
再对 http://localhost:9031/hello/12323 进行访问,毫无疑问,限流效果还是1秒1个请求。
然后再访问 http://localhost:9031/hello/abc 无论我们怎么刷新页面(就相当于访问),都是展示正常的,不会因被限流展示报错页面。然后我们为了展示100确实控制到了,我是用jmeter进行访问。设置好地址 http://localhost:9031/hello/abc 访问量101,一口气全部发送。
执行之后我们可以看到,101个请求只有最后一个失败了,前100个都成功了,证明了我们这个100的限流特例生效了。
post请求的有点问题还没搞懂,对接口已经创建特例了,但是就是没生效,还是访问第二次就报错。
系统自适应限流
在配置页面中是 系统规则
系统规则支持以下的模式:
- 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 达到阈值即触发系统保护。
具体演示就不掩饰了,和之前的没差多少。
@SentinelResource
@SentinelResource注解就相当于Hystrix的@HystrixCommand注解。
@SentinelResource注释指示Sentinel资源的定义。
在之前我们就配置过@SentinelResource这个注解,主不过是不知道它是什么意思、有什么作用而已,这里我们就对它进行一个简单的讲解。
功能与参数
注解参数详细内容查看以下两个链接,这里只是简单介绍:
- CSDN:https://blog.csdn.net/wangguohui0726/article/details/115767107
- 羽雀:https://www.yuque.com/go/doc/43715617
通过查看源码,我们可以看到该注解一共有这么多个属性:
然后我们对它一一进行解释。
- value: Sentinel资源的名称,我们不仅可以通过url进行限流,也可以把此值作为资源名配置,一样可以限流。(下一段就说的这个)
- entryType: 条目类型(入站或出站),默认为出站
- resourceType: 资源的分类(类型)
- blockHandler: 块异常函数的名称,默认为空
- blockHandlerClass: 块处理程序所在的类不应提供多个类
- 默认情况下, blockHandler与原始方法位于同一类中。 但是,如果某些方法共享相同的签名并打算设置相同的块处理程序,则用户可以设置存在块处理程序的类。 请注意,块处理程序方法必须是静态的。
- fallback: 后备函数的名称,默认为空
- defaultFallback: 默认后备方法的名称,默认为空
- defaultFallback用作默认的通用后备方法。 它不应接受任何参数,并且返回类型应与原始方法兼容
- fallbackClass: fallback方法所在的类(仅单个类)
- 默认情况下, fallback与原始方法位于同一类中。 但是,如果某些方法共享相同的签名并打算设置相同的后备,则用户可以设置存在后备功能的类。 请注意,共享的后备方法必须是静态的。
- exceptionsToTrace: 异常类的列表追查, Throwable默认
- exceptionsToIgnore: 要忽略的异常类列表,默认情况下为空
相关问题解决方案
在之前的使用过程中,我们肯定会发现虽然功能是体现出来了,但是我们在实际开发中并不能只是做到这样就行了,我们还需要更丰富完善的功能,所以我们这里提出了几个比较重要的问题以及解决方案。
- 限流返回结果是系统默认的,并没有返回我们想要的结果。
- 依照现有的条件,我们自定义的处理方法又和业务代码耦合在一起,不直观。
- 每个业务方法都添加一个兜底的,那代码膨胀加剧。
- 全局统一的处理方法没有体现。
主要使用@SentinelResource注解的fallback、fallbackClass、blockHandler、blockHandlerClass四个注解实现异常和流控的兜底方案。
规则持久化
我们之前在控制台配置流控、降级规则时候,会发现一个问题,微服务重启了之后相应的规则配置就会自动消失,并没有进行保存,再次使用需要重新配置。这在生产环境上可是巨大的灾难啊,配置了几十个几百个,重启一下,没了,那不能行那不能行,那我们要怎么搞?
我们可以将配置保存在MySQL、nacos……中,这里用nacos举例。
所有能保存的中间件以及保存方式查阅:https://github.com/alibaba/Sentinel/wiki/动态规则扩展
在pom文件中添加如下依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
application.yml中添加:
在nacos的控制台添加如下配置:
[
{
"resource": "/hello/{name}",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
字段含义:
然后,我们访问一次接口之后,可以发现,在Sentinel控制台的流控规则中可以看到我们刚在nacos上配置的规则。