随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制
、熔断降级
、系统负载保护
等多个维度保护服务的稳定性。
《SpringCloud Alibaba Sentinel 官方文档》
《Sentinel github开源代码和文档介绍》
[问:]Sentinel 与 Hystrix 存在哪些不同?
Hystrix
需要引入大量的pom依赖,以及需要在配置文件中做一定量的配置编写;其次使用流量监控等操作时,需要额外配置很多,过于麻烦。
Sentinel
只需要引入对应pom依赖即可,能够在界面上直接进行配置,无需写太多的配置文件。
下载链接
端口占用为 8080 (与tomcat类似)
这是一个jar项目,使用 java -jar xxxxx.jar 启动
查看配置文件,并无端口号相关的配置信息,采取的是Springboot 默认的 8080端口启动。
[注意:]Sentinel需要额外注意端口信息
尝试采取指定端口号启动方式,但无效;可能是官方出现的隐藏bug。
后期如做配置,需要额外注意Sentinel 的端口信息
!
java -jar sentinel-dashboard-1.7.2.jar
访问:
创建子项目 cloudalibaba-Sentinel-Server-8500
引入依赖信息:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
编写配置文件信息,指明当前微服务的端口、服务别名以及让Sentinel监听当前微服务。
## 该服务的端口信息
server:
port: 8500
## 服务别名和nacos服务注册发现地址配置
spring:
application:
name: cloudalibaba-sentiner-server
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel: ## 流控
transport:
# sentinel dashboard的地址(sentinel的地址--8080监控当前8500微服务)
dashboard: localhost:8080
# 为应用开启额外的端口,上报监控信息
# 默认为8719端口,加入被占用将会自动从8719开始+1扫描,直至找到未被占用的端口地址
port: 8719
## 监控相关
management.endpoints.web.exposure.include: '*'
编写启动类和测试类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelApplication8500 {
public static void main(String[] args) {
SpringApplication.run(SentinelApplication8500.class,args);
}
}
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test1")
public String test1(){
return "----this is test1";
}
@RequestMapping("/test2")
public String test2(){
return "----this is test2";
}
}
启动测试项目 cloudalibaba-Sentinel-Server-8500
。
在nacose中完成了服务的注册:
查看Sentinel,但是并无监控信息。
Sentinel是一款
懒加载
式的架构,需要对接口进行请求后,才能获得相关监控信息。
请求接口后再次刷新Sentinel并查看信息统计:
http://localhost:8500/test2
http://localhost:8500/test1
名称 | 介绍 | 注意项 |
---|---|---|
资源名 | 唯一名称 | 默认为请求路径 |
针对资源 | Sentinel可以针对调用者进行限流,填写微服务名 | 默认为default |
阈值类型 QPS | 每秒钟的请求数量 | 当调用该api的QPS达到阈值时,进行限流操作 |
阈值类型 线程数 | 当调用该api的请求线程数达到阈值时,触发限流 | |
流控模式 直接 | 当api达到限流条件时,直接限流。 | |
流控模式 关联 | 当关联的资源达到阈值时,就限流自己。 | |
流控模式 链路 | 当从某个接口过来的资源达到限流条件时,开启限流。 | |
流控效果 快速失败 | 直接进行失败,抛出异常。 | |
流控效果 Warm Up | 根据codeFactor(冷加载因子,默认为3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。 | |
流控效果 排队等待 | 匀速排队,让请求以匀速的速度通过,阈值类型必须设定为QPS,否则无效 |
修改 /test1 请求的流控规则,如下图所示:
默认的高级设置为:
每秒允许请求数为
1
测试需要讲究
快速点击
、缓慢点击
表示
1
秒内,查询1
次请求OK;
如果请求在1
秒内的次数高于1次
,则进入快速失败流程,报默认的错误提示!
修改 /test1
的请求限流配置,将QPS
更改为线程数
。
再次请求:
发现:
无论请求速率多么快,都不会进入
限流回执
。
[问:]这是为什么?
分析问题前,需要了解 QPS
和线程数
这两种方式的区别:
类型 | 区别描述 | 关键 |
---|---|---|
QPS | 假设100个请求同时过来,QPS设置一秒一个请求线程通过,1秒内其他请求直接进行拦截。 | 每秒请求数 |
线程数 | 假设100个请求同时过来,线程数允许所有的请求进入,但每次只允许一个请求执行操作,其他请求数等待。 | 最大请求数 |
修改处理接口:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test1")
public String test1(){
return "----this is test1";
}
@RequestMapping("/test2")
public String test2(){
return "----this is test2";
}
/**
* 模拟 阈值类型 为 线程数
* @return
*/
@RequestMapping("/test3")
public String test3(){
// 每次请求过来,都延时800毫秒做处理
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "----this is test3";
}
}
再次请求测试:
此次的配置采取如下方式:
文档解释现象:
/test1 与 /test2 有关联关系。
若 /test2 达到阈值时,会对 /test1 进行限流
!
实用背景:
如何进行测试呢?
1、正常请求
/test1
,无关乎快速还是缓慢,都会是正常返回信息。
2、循环不断请求/test2
,触发该接口的限流操作。
3、再次请求/test1
,查看请求回执状态信息。
正常请求/test1
:
使用postman、jmeter等工具,迭代请求/test2
,触发限流后,再次请求/test1
查看回执状态信息:
现象:
当/test2
触发了限流,此时的/test1
请求将会受到影响!
当/test2
限流结束,/test1
才能继续请求得到回执!
链路
:当从某个接口过来的资源达到限流条件时,开启限流。
链路流控是针对
上级接口
!
[注意:]此处有巨坑!
1、Sentinel 从
1.6.3
版本开始,Sentinel Web Filter 默认收敛所有URL的入口Context,因此链路限流不生效。
2、1.7.0
版本开始,官方在CommomFilter中引入了一个WEB_CONTEXT_UNIFY
参数,用于控制是否收敛context。默认为true(默认收敛所有),配置为false则可根据不同URL进行链路的限流操作。
3、Spring Cloud Alibaba 在2.1.1.RELEASE
版本后,可以根据配置spring.cloud.sentinel.filter.enabled: false
来关闭自动收敛。
如何进行测试呢?
1、修改父pom中的Spring Cloud Alibaba 的依赖版本信息
<spring.cloud.alibaba.version>2.1.1.RELEASEspring.cloud.alibaba.version>
2、增加一个服务接口,使得两个controller均可调用。
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;
@Service
public class ServiceImpl {
//必须加上 @SentinelResource 注解,此处该注解类似 @HystrixCommand
@SentinelResource("message")
public String message(){
return "message";
}
}
3、编写配置类文件,设置WEB_CONTEXT_UNIFY
为false
。
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterContextConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CommonFilter());
registrationBean.addUrlPatterns("/*");
//入口资源关闭聚合
registrationBean.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY,"false");
registrationBean.setName("sentinelFilter");
registrationBean.setOrder(1);
return registrationBean;
}
}
4、在yml配置文件中,需要新增配置信息
5、编写请求控制器类,调用服务中的方法。
import cn.linkpower.servive.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
/**
*链路demo
*/
@Autowired
private ServiceImpl serviceImpl;
@RequestMapping("/message1")
private String message1(){
serviceImpl.message();
return "lianlu message1";
}
@RequestMapping("/message2")
private String message2(){
serviceImpl.message();
return "lianlu message2";
}
}
6、启动并测试
先分别请求 http://localhost:8500/message1 和 http://localhost:8500/message2 使得Sentinel中存在监控信息。
再次
快速
请求 http://localhost:8500/message1 和 http://localhost:8500/message2
[总结:]
当配置
message资源 链路 /message1 请求接口
时,如果 /message1 出现请求超过限流值,则会触发限流,但 /message2 并不受影响!
反之,/message2 受影响,但 /message1不受影响!
[注:]@SentinelResource 注解在博客下会说明!
此处的效果测试,均采用
阈值类型:QPS
、流控模式:直接
。
直接失败,抛出异常,不做任何额外处理,是最简单的方式。
从开始阈值,到最大QPS阈值会有一个缓冲阶段,
一开始的阈值是最大QPS阈值的1/3,然后缓慢增长,直到最大阈值,适用于突然增大的流量转化为缓步增长。
避免突然大流量造成服务器的宕机!
阈值最初为10;
最初的处理请求数最大为:10/3=3;
在5s时间内,由3缓慢升为10。
突然出现大量请求,不会直接进行实际处理,会
将请求预热后在做处理
!
让请求以均匀的速率通过,单机阈值为每秒通过的请求数,其余的排队等待;
其次,还可以设置一个排队等待的超时时间,若请求在超时时间内还未被处理,则会被丢弃。
达到的目的:
匀速处理多个请求,而不是一开始超过阈值的请求直接失败!
编写测试接口:
/**
* 队列等待模式测试
*/
@RequestMapping("/test4")
public String test4(){
log.info("当前线程执行信息:{}",Thread.currentThread().getName());
return "duilie wait test4";
}
设置 Jmeter 一个线程每秒钟访问俩次:
打印日志信息:
观察发现:
每秒钟限定处理一个请求数。
但是jmeter每秒钟发送2个请求。
设置了延迟等待 500ms后,只有少数请求会进入降级!
对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位)
,那么在接下的时间窗口(DegradeRule 中的timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意Sentinel 默认统计的 RT 上限是 4900 ms
,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx
来配置。每秒请求量 >= N(可配置)
,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)
之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。近 1 分钟的异常数目超过阈值之后会进行熔断
。注意由于统计时间窗口是分钟
级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。1、如果请求的
平均响应数超过阈值(ms)
,则会进入准降级状态
。
2、如果接下来1s内持续进入5个请求,他们的RT
都持续超过了这个阈值
,那么在接下来的时间窗口(s)内
,就会对这个服务进行降级
。
3、RT默认最大为4900ms,如果需要更大,可以通过 -Dcsp.sentinel.statistic.max.rt=xxx 进行配置。
/**
* 降级测试
* @param name
* @return
*/
@RequestMapping("/jiangji")
public String jiangji(@RequestParam("name") String name){
//当请求参数为 1 时,响应延迟600ms 也就是RT可以配置允许时间比这个数低的值
if("1".equalsIgnoreCase(name)){
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("请求的name:{},时间为:{}",String.valueOf(name),new Date().getTime());
return "jiangji test name="+String.valueOf(name)+" 当前时间:"+new Date().getTime();
}
1、正常请求:
http://localhost:8500/jiangji?name=2
不会出现熔断保护。
2、异常请求:
http://localhost:8500/jiangji?name=1
熔断保护时间内,请求正常流程:
http://localhost:8500/jiangji?name=11
保护时间过后,在请求正常流程:
http://localhost:8500/jiangji?name=11
[总结]
1、配置熔断保护的接口,如果不出现超时情况时,不会触发熔断保护降级。
2、当设定RT为500ms后,请求异常流程,1s内平均请求响应都超时并达到对应的数目(测试显示5个),会触发熔断保护,并保护10s(这个时间是时间窗口的配置)。
3、熔断保护期间内,正常请求也会被熔断、降级。
4、10s后,熔断结束,会重新对请求响应时间进行统计判断。
每秒的异常总数,占通过量的比值超过配置的值之后,触发熔断降级。
时间窗口和之前一样,属于熔断保护时间。
占的比值数范围为:[0.0,1.0];
代码案例:
/**
* 异常触发熔断降级测试
*/
int a = 0;
@RequestMapping("/exct")
public String exct(){
a ++ ;
if(a % 3 == 0){
//模拟异常
throw new RuntimeException("请求取余数为0了");
}
return "异常导致的熔断降级测试";
}
暂时未配置时,当请求数每次达到 a % 3 == 0
时,就会产生异常:
由于
a % 3 == 0
触发异常,也就是说异常比例为1/3
。只需要配置的数小于1/3
即可。
http://localhost:8500/exct
正常缓慢请求时,依然和之前一致,出现报错信息。
当请求异常比例大于等于配置数时,出现熔断降级。
资源
1分钟
内的异常总数超过限定的阈值,触发熔断降级!
设定异常数为
3
,表示一分钟请求的异常出现了3个及以上,触发熔断降级!
[注意]此处有坑!
由于异常数是按照
分钟
统计的个数,时间窗口中的设置也必须要大于60s
。
不然会出现:结束熔断保护后仍可能继续熔断保护(不会释放!)
请求测试:
http://localhost:8500/exct
当请求出现异常的次数达到设定的值时,立马出现熔断降级现象。
当熔断保护的时间过了之后,释放,再次请求会重新进行统计!
热点参数流控规则。
何为热点:
热点即经常访问的数据。
热点限流的思想是什么?
假设某一时间段内的某个用户频繁的去访问,就可以采取热点规则方式,对其进行限流操作。
案例测试:
/**
* 热点规则测试项
* @param p1
* @param p2
* @return
*/
@RequestMapping("/hostkey")
//名称唯一 但 热点规则 必须需要此项配置,无此注解不生效
@SentinelResource(value = "hostkey",blockHandler = "fallback_hostkey")
public String testHotKey(@RequestParam(value="p1",required = false) String p1,
@RequestParam(value="p2",required = false) String p2){
return "---- test Hot Key ----";
}
// 修改sentinel 降级的默认回执方法(传递参数的一致性,并且需要携带 BlockException)
public String fallback_hostkey(String p1, String p2, BlockException blockException){
return "限流咯。。。。";
}
当不配置任何规则时,此时的回执信息为:
当请求地址携带p1参数,且每秒钟的请求数高于配置的1次时,触发降级。
请求测试:
1、不携带参数,
快速点击
http://localhost:8500/hostkey
2、携带参数,但不携带p1,快速点击
http://localhost:8500/hostkey?p2=33333
3、携带p1参数,缓慢点击
http://localhost:8500/hostkey?p1=33333
4、携带p1参数,快速点击
针对个别用户频繁访问时,采取了限流操作(按照参数接受类型,但不是具体的数值)。
但是,针对管理员频繁的访问查看或测试数据时,此时不应限流。
配置项:
达到的目的,当p1为520时,限流量放大!
请求测试:
1、缓慢请求,p1非特定值
http://localhost:8500/hostkey?p1=52
2、快速请求,p1非特定值
3、缓慢请求,p1为特定值
http://localhost:8500/hostkey?p1=520
4、快速请求,p1为特定值(每秒请求大于2触发)
修改controller,如下所示:
// 修改sentinel 降级的默认回执方法
public String fallback_hostkey(String p1, String p2, BlockException blockException){
return "限流咯。。。。";
}
/**
* 热点规则测试项
* @param p1
* @param p2
* @return
*/
@RequestMapping("/hostkey2")
//名称唯一 但 热点规则 必须需要此项配置,无此注解不生效
@SentinelResource(value = "hostkey",blockHandler = "fallback_hostkey")
public String testHotKey2(@RequestParam(value="p1",required = false) String p1,
@RequestParam(value="p2",required = false) String p2){
int a = 10/0;
return "---- test Hot Key ----";
}
重启项目,再次访问:
[发现:]
出现了异常,但
未进入blockHandler
中。
[如何解决呢?]
@SentinelResource 其实和 @HystrixCommand 类似,拥有一个 fallback!
修改代码:
// 修改sentinel 降级的默认回执方法
public String fallback_hostkey(String p1, String p2, BlockException blockException){
return "限流咯。。。。";
}
/**
* 热点规则测试项
* @param p1
* @param p2
* @return
*/
@RequestMapping("/hostkey2")
//名称唯一 但 热点规则 必须需要此项配置,无此注解不生效
@SentinelResource(value = "hostkey",blockHandler = "fallback_hostkey",fallback = "except_fallback")
public String testHotKey2(@RequestParam(value="p1",required = false) String p1,
@RequestParam(value="p2",required = false) String p2){
int a = 10/0;
return "---- test Hot Key ----";
}
public String except_fallback(String p1, String p2){
return "出现了异常。。。。。";
}
重启再次请求:
之前的配置操作,采取的是针对单个请求地址
、请求别名
进行配置的限流操作,sentinel同时也提供了一种很潮流的方式实现配置操作—系统自适应限流
。
何为系统自适应限流?
Sentinel 系统自适应限流从
整体维度
对应用入口流量进行控制,结合应用的 Load
、CPU 使用率
、总体平均 RT
、入口 QPS
和并发线程数
等几个维度的监控指标,通过自适应
的流控策略,让系统的入口流量
和系统的负载
达到一个平衡
,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
《系统自适应限流》
文档中,对系统自适应限流有如下几种配置:
阈值类型的含义如下所示:
仅支持Linux/Unix-like
)系统核心数*2.5
)1.5.0+ 版本
)毫秒
。只配置 QPS 项,其他流控模式未配置。
请求测试:
缓慢请求 /test1
快速请求 /test1
缓慢请求 /test2
快速请求 /test2
git地址