Sentinel简介
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
Sentinel比Hystrix更加强大,尤其是Sentinel的流量控制可以控制到每一秒的请求数和并发的线程数。同时,Alibaba给Sentinel也提供了一套Web控制台叫Sentinel Dashboard。相比Hystrix Dashboard只提供流量监控的基础上添加了流量控制,熔断降级等功能。
Sentinel比Hystrix也更加方便,如果使用Hystrix做服务的保护,需要写无穷无尽的保护代码,如果使用Sentinel,不需要写任何代码,只需要在控制台设置即可,可以大大减少代码的复杂度。
Sentinel提供了两个组件:
- 一个是Sentinel,用来实现微服务系统中流量控制、服务熔断、降级等功能。这点和Hystrix类似。
- 一个是Sentinel Dashboard,用来监控微服务系统中流量调用等情况。这点和Hystrix Dashboard类似
Sentinel Dashboard安装
-
-
运行:在安装目录下
Sentinel Dashboard是一个Spring Boot项目,默认端口为8080,如果不外部指定端口,就一定运行不起来。
解决方案有两种:
- 第一种在命令中设置外部端口(如下)。
- 第二种下载Sentinel Source code,在配置文件中修改端口号,再使用Maven将其重新打包使用。
java -Dserver.port=9191 -jar sentinel-dashboard-1.7.2.jar
-
访问Sentinel Dashboard:http://localhost:9191/#/login (用户名和密码默认都是sentinel)
Sentinel流量监控
-
pom.xml中配置依赖
<!-- Sentinel配置 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
nacos配置中心client-dev.yaml中配置
server: port: 9000 spring: cloud: # Nacos配置 nacos: ... # Sentinel设置 sentinel: # 开启Sentinel(默认开启) enabled: true transport: # Sentinel外部访问地址 dashboard: http://localhost:9191 # Sentinel内部通信地址(规定8719) port: 8719
Sentinel Dashboard之所以能够监控各个服务的流量,其原理在于Sentinel Dashboard内部与各个微服务保持通信。各个服务会实时将日志发送给Sentinel Dashboard,然后Sentinel Dashboard分析日志中的信息,最后将数据展示出来。所以Sentinel Dashboard不仅有一个外部访问端口,需要自定义设置(本文设置为9191),还规定了一个内部通信端口(规定为8719)。
-
访问Sentinel Dashboard
流量监控细粒度到每个方法,可以看到各个接口每秒处理和未处理的请求数,图中绿色线条为处理,蓝色线条为未处理。
QPS:全称Queries-per-second,每秒请求数。
如果发现Sentinel Dashboard界面什么都没有,是因为在默认情况下Sentiel为延迟加载,不会在服务启动之后立即创建服务监控,需要对服务进行调用时才会初始化。
Sentinel簇点链路
主要用于展示系统中的接口调用路径。
Sentinel流量控制
流量控制(flow control),其原理是监控接口流量的QPS或并发线程数,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
假如某接口每秒接收1000个请求,但是该接口每秒不能处理1000个请求,这个时候,就要限流。如果不及时限流,在Sentinel Dashboard流量监控面板就会出现该接口通过的QPS一直处于一个高峰的状态,如果一直这样,那么一定会在有限的时间内压垮这台服务。因此可以给该接口的QPS设置一个阈值,比如说每秒最多只处理200个请求,如果有多的请求,全部拒绝处理。
这种场景多用于秒杀,抢购的业务。
Sentinel实现流量控制主要是在Sentinel Dashboard上设置流控规则。
流控规则,降级规则,热点规则等都是仅一次生效,一旦服务重启后,这些设置的规则全部丢失,需要重新设置。因为这些规则都是基于生产上的规则,生产中的服务一旦启动,很少会重启。但是在学习过程中,需要频繁重启,这个时候需要重新设置规则。
限流规则
同一个资源可以创建多条限流规则。FlowSlot会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
-
resource:资源名,即限流规则的作用对象。可以是接口服务路径或自定义的资源名。
-
count:限流阈值
-
grade:限流阈值类型,QPS / 并发线程数。
-
limitApp:流控针对的调用来源,若为default则不区分调用来源,所有来源调用此接口的流量都会受到限制。若为某个路径,比如:/test,那么只会限制 /test 调用该接口的流量。
-
strategy:流控模式,直接 / 关联 / 链路。
-
直接:当本接口达到阈值后,直接触发某种操作。
-
关联:该关联是一个反向关联,当关联接口达到阈值后,本接口直接触发某种操作。(本接口设置的阈值实际上是限制关联接口的)
-
链路:上面两种流控模式都是对某一个接口的流控,而链路是对多个接口的流控。链路通过限制入口接口,当入口接口达到阈值后,这条调用链路上所有接口直接触发某种操作,从而实现了对整个调用链路的流控。
Sentinel为了便于管理,将所有的调用链路都挂在到了一个虚拟节点machine root上,形成一个调用树。
-
-
controlBehavior:流量控制效果,快速失败 / Warm Up / 匀速排队
-
快速失败:直接拒绝,是默认的流量控制方式,当QPS或并行线程数超过阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。
-
Warm up:预热 / 冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
配置Warm up时,需要提供预热时常,假设为10s。同时Warm up内部设置一个预热因子为3。如下配置表示在流量忽然增加超过了阈值达到每秒30个请求时,开启冷启动。每秒先处理 30 / 3 ≈ 10个,然后逐渐增长每秒处理11个,每秒处理12个,在10s内逐步增长到每秒处理20个,然后稳定在每秒处理20个请求的水平,每秒多余的10个请求直接拒绝。
-
排队等待:严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
配置排队等待时,需要提供超时时间,假设为10000ms(10s)。当接收到请求时,所有请求进入一个队列,每秒处理的请求数 = 单机阈值 = 20个,按照排队的顺序每50ms(1000ms / 20个 = 50ms)处理一个请求,持续处理10s。超过10s之后,剩下还未处理的请求直接拒绝。
-
基于QPS的流控
流控原理示意图(以直接快速失败为例):
基于并发线程数的流控
流控原理示意图(以直接快速失败为例):
在同一时刻,一个线程只能处理一个请求。
总结
基于QPS的流控主要是限制外部到内部的请求数,从而达到流控。
基于并发线程数的流控只要是限制内部的处理能力,从而达到流控。
两种流控的最终目的都是保证在高流量场景下,服务不被压垮。
Sentinel熔断降级策略
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或资源出现异常的比例升高),就对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException )。
平均响应时间(DEGRADE_GRADE_RT)
当1s内持续进入N(N >= 5)个请求,对应时刻的平均响应时间均超过阈值RT(以ms为单位) ,那么在接下来的时间窗口(以s为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException)。注意Sentinel默认统计的RT上限是4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
如下配置表示,给 /test 接口设置平均响应时间的阈值为1000ms,如果在同一时刻大于5个请求访问 /test 接口且平均响应时间均大于1s,那么立即触发熔断,熔断持续时间为10s,在10s内访问 /test 的请求全部拒绝。
异常比例(DEGRADE_GRADE_EXCEPTION_RATIO)
当1s内持续进入N(N >= 5)个请求,并且每秒异常总数占通过量的比值超过阈值(小数)之后,资源进入降级状态,即在接下的时间窗口(以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0,1.0],代表 0% ~ 100%。
如下配置表示,给 /test 接口设置异常比例的阈值为0.8,如果在同一时刻大于5个请求访问 /test 接口且触发异常的接口比例超过80%,那么立即触发熔断,熔断持续时间为10s,在10s内访问 /test 的请求全部拒绝。
异常数(DEGRADE_GRADE_EXCEPTION_COUNT )
当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若时间窗口小于60s,则结束熔断状态后仍可能再进入熔断状态。
如下配置表示,给 /test 接口设置异常数为10,如果在一分钟之内访问 /test 接口的请求触发异常的总数大于等于10,那么会立即触发熔断,熔断持续时间为120s,在120s内访问 /test 的请求全部拒绝。
@SentinelResource注解
流控熔断异常处理
在前文中的流控抛出的异常为FlowException,熔断降级抛出的异常为DegradeException,这两个异常都是BlockException的子类,因此会显示如下页面:
出了上面两个异常,还有ParamFlowException,AuthorityException,SystemBlockException都是BlockException的子类。分别对应着热点规则,授权规则,系统规则出现的异常。
如果想自定义触发流控降级异常之后的操作,需要设置@SentinelResource注解和自定义降级方法。
@SentinelResource中的value属性表示资源名,在Sentinel Dashboard中配置流控和降级表单中的资源名那一栏可以直接填写value的值而不用接口路径。
@SentinelResource中的blockHandler属性表示捕捉到Sentinel内部的五个异常(FlowException,DegradeException ...)之后调用的降级方法名。
降级方法名可以不和原方法一样(建议为原方法名 + Handler),但是参数和返回值必须和原方法一致,且降级方法的最后一个参数必须为BlockException。如果需要监控流控异常和熔断降级异常,那么降级方法内部需要进行instanceof判断。
@GetMapping("/test")
@SentinelResource(value = "test", blockHandler = "testHandler")
public String test(String message) {
System.out.println(message);
return message;
}
// Sentinel异常降级方法
public String testHandler(String message, BlockException blockException) {
if (blockException instanceof FlowException) {
return "当前服务被限流";
} else if (blockException instanceof DegradeException) {
return "当前服务被降级";
}
return "当前服务不可用";
}
其他异常处理
如果想自定义触发其他异常之后的操作,需要设置@SentinelResource注解和自定义降级方法。
@SentinelResource中的fallback属性表示捕捉到其他异常(包括所有运行时异常)之后调用的降级方法名。
降级方法名可以不和原方法一样(建议为原方法名 + Fallback),但是参数和返回值必须和原方法一致,且可以定义一个Throwable类型的异常作为降级方法的最后一个参数(一般不定义)。如果需要同时监控异常,那么降级方法内部也需要进行instanceof判断。
@GetMapping("/test")
@SentinelResource(value = "test", blockHandler = "testHandler", fallback = "testFallback")
public String test(String message) {
if (message == null) {
throw new RuntimeException("参数为空");
}
System.out.println(message);
return message;
}
// Sentinel异常降级方法
public String testHandler(String message, BlockException blockException) {
if (blockException instanceof FlowException) {
return "当前服务被限流";
} else if (blockException instanceof DegradeException) {
return "当前服务被降级";
}
return "当前服务不可用";
}
// 其他运行时异常降级方法
public String testFallback(String message) {
return "参数为空,当前服务不可用";
}