目录
Sentinel概述
基本介绍
Sentinel 基本核心概念
Sentinel安装
简单安装启动
启动配置项
SpringCloud简单整合
实战架构
父工程pom文件
teacher-service服务
student-service服务
测试
整合Sentinel
SpringCloud微服务保护方案解读
服务雪崩定义
问题的产生
示例
雪崩产生的几种场景
解决方案
熔断模式
隔离模式(仓壁模式 )
限流模式
超时处理
总结
服务保护技术对比
一文带你速通Sentinel限流规则(流控)解读
前置知识速补
基本介绍
流控模式
直接模式
关联模式
链路模式
流控效果
直接失败
Warm Up(预热)
排队等待
一文速通Sentinel熔断及降级规则
基本介绍
熔断模式
状态机的三个状态
熔断降级规则
断路器熔断策略
慢调用
异常比例
异常数
Sentinel热点参数限流动
什么是热点
全局参数限流
热点参数限流
热点参数规则
Sentinel黑白名单授权规则解读
基本介绍
代码实战
架构说明
RequestOriginParser的实现类
网关添加请求头
配置授权规则
Sentinel概述
基本介绍
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 基本核心概念
资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
Sentinel安装
简单安装启动
sentinel官方提供了UI控制台,方便我们对系统做限流设置。可以在GitHub下载。
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。这里,我们将会详细讲述如何通过简单的步骤就可以使用这些功能。
Sentinel 控制台包含如下功能:
-
查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
-
监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
-
规则管理和推送:统一管理推送规则。
-
鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
下载地址: Releases · alibaba/Sentinel · GitHub
将jar包放到任意非中文目录
启动jar包:
java -jar sentinel-dashboard-1.8.1.jar
指定端口:
java -jar sentinel-dashboard-1.8.1.jar -Dserver.port=8090
修改其他配置看官网: home | Sentinel
访问 localhost:8080 默认账号密码都是:sentinel
可以看到现在是一片空白的,稍后整合服务以后就可以出现信息的。
启动配置项
Sentinel 提供如下的配置方式:
-
JVM -D 参数方式
-
properties 文件方式(1.7.0 版本开始支持)
其中,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 | 默认密码 |
SpringCloud简单整合
实战架构
我们进行整合的时候,先搭建一个简单的微服务项目架构。
俩个为俩个不同的端口的service端,客户端向8002端口的studentservice发送一个请求(/getInfo/{id})以后,8002端口的studentservice需要往teacherservice发送一个请求(/getTeacher/{id})返回数据。
父工程pom文件
统一管理版本信息
<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>
teacher-service服务
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: teacherservicecloud: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,"男");}
}
student-service服务
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: studentservicecloud: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的注解来声明远程调用的信息,比如:
-
服务名称:teacherservice
-
请求方式:GET
-
请求路径:/getTeacher/{id}
-
请求参数:String id
-
返回值类型:Teacher
这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。
StudentController
@RestController
public class StudentController implements Serializable {@AutowiredTeacherServiceFeign 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": "男" }
整合Sentinel
我们在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整合
SpringCloud微服务保护方案解读
服务雪崩定义
我们都知道在微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。这种在微服务调用链路中,因为某个服务不可用导致上游服务调用者不可用,最终扩大至整个服务集群产生不可用的问题称之为雪崩效应(一个不可用导致全部不可用)。
分布式系统都存在这样一个问题,由于网络的不稳定性,决定了任何一个服务的可用性都不是 100% 的。当网络不稳定的时候,作为服务的提供者,自身可能会被拖死,导致服务调用者阻塞,最终可能引发雪崩连锁效应。
问题的产生
示例
下面用一个例子来演示这个问题:现在有服务A,他的业务涉及到调用服务1,服务2,服务3。
如果服务提供者服务1发生了故障,当前的应用的部分业务因为依赖于服务1,因此也会被阻塞。此时,其它不依赖于服务1的业务似乎不受影响。
但是,依赖服务1的业务请求被阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:
编辑
由于服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,那么当前服务也就不可用了。那么,依赖于当前服务的其它服务(服务2,服务3)随着时间的推移,最终也都会变的不可用,形成级联失败,雪崩就发生了:
服务调用者不可用的主要原因是当服务调用者使用同步调用时,会产大量的线程等待占用系统资源
。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是服务雪崩效应就产生了。
雪崩产生的几种场景
-
流量激增:比如异常流量、用户重试导致系统负载升高;
-
缓存刷新:假设A为client端,B为Server端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃;
-
程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;
-
硬件故障:比如宕机,机房断电,光纤被挖断等。
-
数据库严重瓶颈,比如:长事务、sql超时等。
-
线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩;
解决方案
一般情况对于服务依赖的保护主要有4种解决方案:
熔断模式
主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。断路器会统计访问某个服务的请求数量,异常比例或异常数量,当发现访问服务C的请求异常比例过高时,认为服务C有导致雪崩的风险,会拦截访问服务C的一切请求,形成熔断。
设置异常/失败/慢调用上限,当达到上限就断开调用,再来的请求都不让调用,隔断时间之后再尝试调用,如果服务还能运行再放行,否则继续拒绝。
隔离模式(仓壁模式 )
对系统请求按类型划分成一个个小岛的一样,当某个小岛被火少光了,不会影响到其他的小岛。例如:可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。
我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
限流模式
熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。
流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。
超时处理
设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待。超时分两种,一种是请求的等待超时,一种是请求运行超时。
-
等待超时:在任务入队列时设置任务入队列时间,并判断队头的任务入队列时间是否大于超时时间,超过则丢弃任务。
-
运行超时:直接可使用线程池提供的get方法。
(1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
(2)信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
总结
限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。超时处理、线程隔离、降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种补救措施。
服务保护技术对比
在SpringCloud当中支持多种服务保护技术:
-
Netfix Hystrix
-
Sentinel
-
Resilience4J
早期比较流行的是Hystrix框架,但目前国内实用最广泛的还是阿里巴巴的Sentinel框架,这里我们做下对比:
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于慢调用比例或异常比例 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速排队模式 | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
速通Sentinel限流规则(流控)解读
前置知识速补
-
QPS每秒查询率(Query Per Second):每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。 (看来是类似于TPS,只是应用于特定场景的吞吐量)
-
吞吐量(Throughput):吞吐量是指系统在单位时间内处理请求的数量。对于无并发的应用系统而言,吞吐量与响应时间成严格的反比关系,实际上此时吞吐量就是响应时间的倒数。前面已经说过,对于单用户的系统,响应时间(或者系统响应时间和应用延迟时间)可以很好地度量系统的性能,但对于并发系统,通常需要用吞吐量作为性能指标。
-
资源: 是 Sentinel 中的核心概念之一。最常用的资源是我们代码中的 Java 方法,一段代码,或者一个接口。
基本介绍
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
-
resource:资源名,即限流规则的作用对象
-
count: 限流阈值
-
grade: 限流阈值类型,QPS 或线程数
-
strategy: 根据调用关系选择策略
-
controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 或线程数模式 1. QPS:每秒请求数,当前调用该api的QPS到达阈值的时候进行限流 2. 线程数:当调用该api的线程数到达阈值的时候,进行限流 | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 | 直接拒绝 |
同一个资源可以同时有多个限流规则。
我们可以通过代码定义流量控制规则也可以通过在sentinel控制台进行配置,下面是一个代码配置的方式:
@Service
public class TestService {
@PostConstructpublic 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,快速失败是最常用的处理形式。
Warm Up(预热)
阈值一般是一个微服务能承担的最大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执行一个请求,那么:
-
第6个请求的预期等待时长 = 200 * (6 - 1) = 1000ms
-
第12个请求的预期等待时长 = 200 * (12-1) = 2200ms
也就是第12个请求会被拒绝,其他请求会进入等待队列
速通Sentinel熔断及降级规则解读
基本介绍
熔断模式
主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。
编辑
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
状态机的三个状态
状态机包括三个状态:
-
closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
-
open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
-
half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
-
请求成功:则切换到closed状态
-
请求失败:则切换到open状态
-
编辑
熔断降级规则
熔断降级规则包含下面几个重要的属性:
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次,则触发熔断。
速通Sentinel热点参数限流动解读
什么是热点
限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
-
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
-
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
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黑白名单授权规则解读
基本介绍
授权规则可以对请求方来源做判断和控制。
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(
origin
)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。
-
白名单:来源(origin)在白名单内的调用者允许访问
-
黑名单:来源(origin)在黑名单内的调用者不允许访问
编辑
来源访问控制规则(AuthorityRule
)非常简单,主要有以下配置项:
-
resource
:资源名,即限流规则的作用对象。 -
limitApp
:对应的黑名单/白名单,不同 origin 用,
分隔,如appA,appB
。 -
strategy
:限制模式,AUTHORITY_WHITE
为白名单模式,AUTHORITY_BLACK
为黑名单模式,默认为白名单模式。
比如我们希望控制对资源 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阻塞限制访问。
RequestOriginParser的实现类
再service服务中,我们定义一个RequestOriginParser的实现类:
@Component
public class HeaderOriginParser implements RequestOriginParser {@Overridepublic 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,gatewayroutes:# ...略
这样,从gateway路由的所有请求都会带上origin头,值为gateway。而从其它地方到达微服务的请求则没有这个头。
配置授权规则
添加一个授权规则,放行origin值为gateway的请求。