最近在查阅Sentinel相关内容的时候,发现很少有文章能把Sentinel的全部知识点做一个通俗易懂的总结,大多零零散散或者直接照搬官网,把握不住重点脱离实际开发,趁着腾讯云在举办“2023腾讯·技术创作特训营 第三期”,于是心血来潮直接总结了一波,本文大概2w字,硬核程度超乎你的想象,新手可快速把握,老鸟可以迅速回顾知识点增强理解,下面的思维导图是文章的主要内容,大家如果要看哪个知识点可以直接定位到对应知识点观看即可,本文基于个人理解,难免会有一些小差错,如果有差错还请指正,如果大家有什么不懂的地方可以到文章下方留言。最后,希望这篇文章能帮助到大家。
🍻 本文字数:约2w
☕ 推荐阅读时间:约40min-50min
👉 单节阅读时间:7min
Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:home | Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。*Sentinel* 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel 具有以下特征:
•丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
•完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
•广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
•完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等
早期比较流行的是Hystrix框架,但目前国内实用最广泛的还是阿里巴巴的Sentinel框架,这里我们做下对比:
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于慢调用比例或异常比例 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速排队模式 | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
sentinel官方提供了UI控制台,方便我们对系统做限流设置。可以在GitHub下载。
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。这里,我们将会详细讲述如何通过简单的步骤就可以使用这些功能。
Sentinel 控制台包含如下功能:
下载地址: https://github.com/alibaba/Sentinel/releases
将jar包放到任意非中文目录
启动jar包:
java -jar sentinel-dashboard-1.8.1.jar
指定端口:
java -jar sentinel-dashboard-1.8.1.jar -Dserver.port=8090
修改其他配置看官网: https://sentinelguard.io/zh-cn/
访问 localhost:8080 默认账号密码都是:sentinel
可以看到现在是一片空白的,稍后整合服务以后就可以出现信息的。
Sentinel 提供如下的配置方式:
其中,project.name 参数只能通过 JVM -D 参数方式配置(since 1.8.0 取消该限制),其它参数支持所有的配置方式。
优先级顺序:JVM -D 参数的优先级最高。若 properties 和 JVM 参数中有相同项的配置,以 JVM 参数配置的为准。
用户可以通过 -Dcsp.sentinel.config.file 参数配置 properties 文件的路径,支持 classpath 路径配置(如 classpath:sentinel.properties)。默认 Sentinel 会尝试从 classpath:sentinel.properties 文件读取配置,读取编码默认为 UTF-8。
如果要修改Sentinel的默认端口、账户、密码,可以通过下列配置:
配置项 | 默认值 | 说明 |
---|---|---|
server.port | 8080 | 服务端口 |
sentinel.dashboard.auth.username | sentinel | 默认用户名 |
sentinel.dashboard.auth.password | sentinel | 默认密码 |
我们进行整合的时候,先搭建一个简单的微服务项目架构。
俩个为俩个不同的端口的service端,客户端向8002端口的studentservice发送一个请求(/getInfo/{id})以后,8002端口的studentservice需要往teacherservice发送一个请求(/getTeacher/{id})返回数据。
统一管理版本信息
<groupId>org.example</groupId>
<artifactId>eurek-test</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eurek-serve</module>
<module>student-service</module>
<module>teacher-service</module>
</modules>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
<mysql.version>5.1.47</mysql.version>
<mybatis.version>2.1.1</mybatis.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--nacos的管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
启动类
@SpringBootApplication
public class TeacherApplication {
public static void main(String[] args) {
SpringApplication.run(TeacherApplication.class,args);
}
}
yml配置文件
server:
port: 8002
spring:
application:
name: teacherservice
cloud:
nacos:
server-addr: localhost:8848
Teacher类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher implements Serializable {
private String name;
private String sex;
}
TeachertController
@RestController
public class TeacherController {
@GetMapping("/getTeacher/{id}")
public Teacher getInfo(@PathVariable("id") String id){
return new Teacher("张三-"+id,"男");
}
}
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
yml配置文件
server:
port: 8002
spring:
application:
name: studentservice
cloud:
nacos:
server-addr: localhost:8848
启动类
@SpringBootApplication
@EnableFeignClients
public class StudentApplication {
public static void main(String[] args) {
SpringApplication.run(StudentApplication.class,args);
}
}
Teacher类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher implements Serializable {
private String name;
private String sex;
}
TeacherServiceFeign
@FeignClient("teacherservice")
public interface TeacherServiceFeign {
@GetMapping("/getTeacher/{id}")
Teacher getInfo(@PathVariable("id") String id);
}
个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。
StudentController
@RestController
public class StudentController implements Serializable {
@Autowired
TeacherServiceFeign teacherServiceFeign;
@GetMapping("/getInfo")
public Teacher getInfo(){
Teacher teacher = teacherServiceFeign.getInfo("111");
return teacher;
}
}
GET http://localhost:8002/getInfo HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Tue, 17 Oct 2023 02:46:43 GMT Keep-Alive: timeout=60 Connection: keep-alive { "name": "张三-111", "sex": "男" }
我们在student-service服务的pom文件中添加以下依赖:
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改application.yaml文件,添加下面内容:
server:
port: 8088
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
重新启动student-service。
GET http://localhost:8002/getInfo HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Thu, 26 Oct 2023 02:58:44 GMT Keep-Alive: timeout=60 Connection: keep-alive { "name": "张三-111", "sex": "男" }
查看sentinel控制台:
但注意默认只有controller中的方法会被监控,Service层的方法没有被监控,也就不能配置限流规则,要标记其它方法,需要利用@SentinelResource注解。 链路模式中,是对不同来源的两个链路做监控。但是sentinel默认会给进入SpringMVC的所有请求设置同一个root资源,会导致链路模式失效。 我们需要关闭这种对SpringMVC的资源聚合,修改服务的application.yml文件: spring: cloud: sentinel: web-context-unify: false # 关闭context整合
我们都知道在微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。这种在微服务调用链路中,因为某个服务不可用导致上游服务调用者不可用,最终扩大至整个服务集群产生不可用的问题称之为雪崩效应(一个不可用导致全部不可用)。
分布式系统都存在这样一个问题,由于网络的不稳定性,决定了任何一个服务的可用性都不是 100% 的。当网络不稳定的时候,作为服务的提供者,自身可能会被拖死,导致服务调用者阻塞,最终可能引发雪崩连锁效应。
下面用一个例子来演示这个问题:现在有服务A,他的业务涉及到调用服务1,服务2,服务3。
如果服务提供者服务1发生了故障,当前的应用的部分业务因为依赖于服务1,因此也会被阻塞。此时,其它不依赖于服务1的业务似乎不受影响。
但是,依赖服务1的业务请求被阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:
由于服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,那么当前服务也就不可用了。那么,依赖于当前服务的其它服务(服务2,服务3)随着时间的推移,最终也都会变的不可用,形成级联失败,雪崩就发生了:
服务调用者不可用的主要原因是当服务调用者使用同步调用时,会产大量的线程等待占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是服务雪崩效应就产生了。
一般情况对于服务依赖的保护主要有4种解决方案:
主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。断路器会统计访问某个服务的请求数量,异常比例或异常数量,当发现访问服务C的请求异常比例过高时,认为服务C有导致雪崩的风险,会拦截访问服务C的一切请求,形成熔断。
设置异常/失败/慢调用上限,当达到上限就断开调用,再来的请求都不让调用,隔断时间之后再尝试调用,如果服务还能运行再放行,否则继续拒绝。
对系统请求按类型划分成一个个小岛的一样,当某个小岛被火少光了,不会影响到其他的小岛。例如:可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。
我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。
流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。
设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待。超时分两种,一种是请求的等待超时,一种是请求运行超时。
(1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理) (2)信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。超时处理、线程隔离、降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种补救措施。
我们在这里再次对比下SpringCloud当中支持多种服务保护技术:
早期比较流行的是Hystrix框架,但目前国内实用最广泛的还是阿里巴巴的Sentinel框架,这里我们做下对比:
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于慢调用比例或异常比例 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速排队模式 | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 或线程数模式 1. QPS:每秒请求数,当前调用该api的QPS到达阈值的时候进行限流 2. 线程数:当调用该api的线程数到达阈值的时候,进行限流 | QPS 模式 |
limitApp | 流控针对的调用来源 | default,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 | 直接拒绝 |
同一个资源可以同时有多个限流规则。
我们可以通过代码定义流量控制规则也可以通过在sentinel控制台进行配置,下面是一个代码配置的方式:
@Service
public class TestService {
@PostConstruct
public void init(){
initFlowRule();
}
private static void initFlowRule(){
//流控规则集合
List<FlowRule> rules = new ArrayList<>();
//创建规则
FlowRule rule = new FlowRule();
//设置受保护的资源
rule.setResource("sayHello");
//设置流控规则 QPS 限流阈值类型:QPS、并发线程数
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源的阈值
rule.setCount(2);
//设置流控手段:快速失败
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rules.add(rule);
//加载配置好的规则
FlowRuleManager.loadRules(rules);
}
@SentinelResource(value = "sayHello")
public String sayHello(String name) {
return "Hello, " + name;
}
}
在添加限流规则时,点击高级选项,可以选择三种流控模式:
直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。
示例:
其含义是限制 /getInfo这个资源的单机QPS为1,即每秒只允许1次请求,超出的请求会被拦截并报错。
关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流。
调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系。Sentinel 通过 NodeSelectorSlot 建立不同资源间的调用的关系,并且通过 ClusterBuilderSlot 记录每个资源的实时统计信息。当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。
比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢.
示例:
当/write资源访问量触发阈值(也就是5时)时,就会对/read资源限流,避免影响/write资源。
链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
假设现在某公司开发了一个单机的电商系统,为了满足完成“下订单”的业务,程序代码会依次执行订单创建方法->减少库存方法->微信支付方法->短信发送方法。方法像链条一样从前向后依次执行,这种执行的链条被称为调用链路在。
比如某个微服务中 /get接口,会被 /pay接口调用。在另一个业务,List 接口也会被 /shop接口调用。
但如果按下图配置,将入口资源设为“/pay”,则只会针对 pay接口的调用链路生效。当访问 pay接口的QPS 超过 1 时,get接口就会被限流。而另一条链路从 shop接口到get接口的链路则不会受到任何影响。
链路模式与关联模式最大的区别是 /get接口与 /pay接口必须是在同一个调用链路中才会限流,而关联模式是任意两个资源只要设置关联就可以进行限流。
快速失败是指流量当过限流阈值后,直接返回响应并抛出 BlockException,快速失败是最常用的处理形式。
阈值一般是一个微服务能承担的最大QPS,但是一个服务刚刚启动时,一切资源尚未初始化(冷启动),如果直接将QPS跑到最大值,可能导致服务瞬间宕机。
warm up也叫预热模式,是应对服务冷启动的一种方案。Warm Up 用于应对瞬时大并发流量冲击。请求阈值初始值是 maxThreshold / coldFactor,持续指定时长后,逐渐提高到maxThreshold值。而coldFactor的默认值是3。
例如:List 接口平时单机阈值 QPS 处于低水位:默认为 1000/3 (冷加载因子)≈333,当瞬时大流量进来,10 秒钟内将 QPS 阈值逐渐拉升至 1000,为系统留出缓冲时间,预防突发性系统崩溃。预热10秒后,慢慢将阈值升至1000。刚开始刷/;List,会出现默认错误,预热时间到了后,阈值增加,没超过阈值刷新,请求正常。
当请求超过QPS阈值时,快速失败和warm up 会拒绝新的请求并抛出异常。而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
Sentinel会以固定的间隔时间让请求通过, 访问资源。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过;反之,则马上抛出阻塞异常。
工作原理:
例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待时长超过2000ms的请求会被拒绝并抛出异常。
那什么叫做预期等待时长呢?
比如现在一下子来了12 个请求,因为每200ms执行一个请求,那么:
也就是第12个请求会被拒绝,其他请求会进入等待队列
主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
状态机包括三个状态:
熔断降级规则包含下面几个重要的属性:
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 引入) |
断路器熔断策略有三种:慢调用、异常比例、异常数。
慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
RT超过500ms的调用是慢调用,统计最近10000ms内的请求,如果请求量超过10次,并且慢调用比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
异常比例:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值,则触发熔断。
异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于0.4,则触发熔断。
异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的超过指定异常数,则触发熔断。
异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于2次,则触发熔断。
限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
注意事项:热点参数限流对默认的SpringMVC资源无效,需要利用@SentinelResource注解标记资源
对以下资源进行热点参数限流
@GetMapping("/getInfo/{id}")
@SentinelResource("/hot")
public Teacher getInfo(@PathVariable String id){
Teacher teacher = teacherServiceFeign.getInfo(id);
return teacher;
}
代表的含义是:对hot这个资源的0号参数(第一个参数)做统计,每1秒相同参数值的请求数不能超过5
当id=1的请求触发阈值被限流时,id值不为1的请求不受影响。
刚才的配置中,对查询信息这个接口的所有商品一视同仁,QPS都限定为2。而在实际开发中,可能部分id是热点id,例如秒杀商品,我们希望这部分商品的QPS限制与其它商品不一样,高一些。那就需要配置热点参数限流的高级选项了。
对以下资源进行热点参数限流:
@GetMapping("/getInfo/{id}")
@SentinelResource("/hot")
public Teacher getInfo(@PathVariable String id){
Teacher teacher = teacherServiceFeign.getInfo(id);
return teacher;
}
给hot这个资源添加热点参数限流,规则如下: •默认的热点参数规则是每1秒请求量不超过3 •给1这个参数设置例外:每1秒请求量不超过2 •给2这个参数设置例外:每1秒请求量不超过1
热点参数规则(ParamFlowRule)类似于流量控制规则(FlowRule):
属性 | 说明 | 默认值 |
---|---|---|
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 | 集群流控相关配置 |
我们可以通过 ParamFlowRuleManager 的 loadRules 方法更新热点参数规则,下面是一个示例:
ParamFlowRule rule = new ParamFlowRule(resourceName)
.setParamIdx(0)
.setCount(5);
// 针对 int 类型的参数 PARAM_B,单独设置限流 QPS 阈值为 10,而不是全局的阈值 5.
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B))
.setClassType(int.class.getName())
.setCount(10);
rule.setParamFlowItemList(Collections.singletonList(item));
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
授权规则可以对请求方来源做判断和控制。
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。
来源访问控制规则(AuthorityRule)非常简单,主要有以下配置项:
比如我们希望控制对资源 test 的访问设置白名单,只有来源为 appA 和 appB 的请求才可通过,则可以配置如下白名单规则:
AuthorityRule rule = new AuthorityRule();
rule.setResource("test");
rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
rule.setLimitApp("appA,appB");
AuthorityRuleManager.loadRules(Collections.singletonList(rule));
Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。
public interface RequestOriginParser {
/**
* 从请求request对象中获取origin,获取方式自定义
*/
String parseOrigin(HttpServletRequest request);
}
这个方法的作用就是从request对象中,获取请求者的origin值并返回。
默认情况下,sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请求的来源都被认为是一样的值default。因此,我们需要自定义这个接口的实现,让不同的请求,返回不同的origin。
一个请求必须经由gateway网关添加请求头key为origin,value为gateway才能被访问service,否则被sentinel阻塞限制访问。
再service服务中,我们定义一个RequestOriginParser的实现类:
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 1.获取请求头
String origin = request.getHeader("origin");
// 2.非空判断
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}
我们会尝试从request-header中获取origin值。
既然获取请求origin的方式是从reques-header中获取origin值,我们必须让所有从gateway路由到微服务的请求都带上origin头。
利用一个GatewayFilter来实现,AddRequestHeaderGatewayFilter。修改gateway服务中的application.yml,添加一个defaultFilter:
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,gateway
routes:
# ...略
这样,从gateway路由的所有请求都会带上origin头,值为gateway。而从其它地方到达微服务的请求则没有这个头。
添加一个授权规则,放行origin值为gateway的请求。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。