1.引言
我们成功搭建了sentinel流量监控平台,并且实现了对我们微服务应用的监控功能,但是sentinel提供的功能不仅仅只有这些,还有强大的流量控制功能。
2.相关术语
资源名:唯一名称,默认请求路径;
针对来源:Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制);针对调用同一个资源时,Sentinel是能够区分不同调用者,为不同的调用者设置不一样的流控规则;如:app-a设置QPS类型的流控,app-b设置线程数类型的流控。默认default,表示不区分调用者。
阈值类型/单机阈值:
QPS:每秒钟的请求数量,当调用接口的QPS达到阈值的时候,进行限流;
线程数:当调用接口的线程数达到阈值的时候,进行限流;
是否集群:不需要集群
流控模式:
直接:接口达到限流条件时,直接限流;
关联:当关联的资源达到阈值时,就限流自己;
链路:只记录指定链路上的流量 (指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】;
流控效果:
快速失败:直接失败,抛异常;
Warm Up:根据codeFactor(冷加载因子,默认为3)的值,即请求 QPS 从阈值 / codeFactor,经过预热时长,逐渐升至设定的QPS阈值;
排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效;
3.Sentinel流控模式 - QPS直接失败
QPS是什么? 简单理解,QPS就是每秒请求数量。
Sentinel的流控模式代表的流控的方式,默认【直接】,还有关联,链路。
Sentinel的流控效果:默认【快速失败】,还有WarmUp,排队等待。
下面我们通过一个示例说明QPS直接失败,具体配置如下:
【a】点击"簇点链路",查看当前有哪些资源可以进行流量控制
【b】新增QPS阈值,快速失败规则,具体配置见下图
添加完成后如下图:
【c】测试
浏览器访问,http://localhost:8401/sentinel,我们控制1秒内只访问两次该接口:
可以看到,在没有达到QPS(每秒请求两次)的阈值时,我们的/sentinel资源是正常访问的,下面我们疯狂点击,手动触发1秒内不止发起两个请求:http://localhost:8401/sentinel
可以看到,/sentinel资源被sentinel限流了,只要不超过QPS的阈值,接口是能够正常被访问的。
当调用该接口的QPS达到阈值的时候,进行限流。
4.Sentinel流控模式 - 线程数直接失败
【a】新增线程数阈值,快速失败规则,具体配置见下图
【b】改造/sentinel接口,增加模拟网络延时
package com.bruce.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class SentinelController {
@GetMapping("/sentinel")
public String sentinel() {
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello, sentinel dashboard....";
}
}
【c】测试
打开两个浏览器窗口,分别访问:http://localhost:8401/sentinel,如果我们按照正常速度访问的话:
可以看到,是没有什么问题的,但当我们快速切换两个浏览器窗口时,疯狂点击刷新按钮,触发超过线程数阈值:
可见,如果一秒内访问资源的线程数量超过1个,那么就会触发sentinel的限流。
总结:当线程A过来访问该接口,该请求处理的很慢,还没有返回数据;此时线程B也过来访问该接口,线程B访问接口则会被限流
5.Sentinel流控模式 - 关联
关联:当关联的资源达到阈值的时候,就限流自己,关联模式的核心就是保护关联资源的。举个例子,当与A关联的资源B达到阈值时,就限流A自己。
【a】因为要做资源关联,所以控制层新增如下方法
package com.bruce.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class SentinelController {
@GetMapping("/sentinel")
public String sentinel() {
return "hello, sentinel dashboard....";
}
@GetMapping("/sentinel2")
public String sentinel2() {
return "hello, sentinel2 dashboard....";
}
}
【b】配置/sentinel资源关联/sentinel2资源,当/sentinel2资源达到阈值时,限流/sentinel资源。具体配置如下图:
【c】使用postman模拟密集访问/sentinel2资源
【d】测试
当postman还在运行的时候,我们浏览器访问:http://localhost:8401/sentinel
可以看到,当/sentinel2资源达到阈值后,/sentinel资源就无法访问了,被限流,这就是关联。
分析:因为我们设置/sentinel资源关联/sentinel2资源,并且设置的QPS阈值为2,所以当1秒内访问/sentinel2的次数大于2时,/sentinel将会被限流,注意,并不是限制访问的自身资源(/sentinel2),而是/sentinel。(是不是感觉很霸道,关联资源达到阀值,是本资源接口被限流了)
这种关联模式有什么应用场景呢?
我们举个例子,订单服务中会有2个重要的接口,一个是读取订单信息接口,一个是写入订单信息接口。
在高并发业务场景中,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能。业务中如果我们希望写入订单比较重要,要优先考虑写入订单接口。
那就可以利用关联模式:在关联资源上面设置写入接口,资源名设置读取接口就行了;这样就起到了优先写入,一旦写入请求多,就限制读的请求。
6.Sentinel流控模式 - 链路
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,就开启限流;
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。
【a】新增业务层接口
package com.bruce.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* @BelongsProject: springcloud-alibaba-nacos
* @BelongsPackage: com.bruce.service
* @CreateTime: 2021-02-20 15:14
* @Description: TODO
*/
@Service
public class SentinelService {
private static final Logger logger = LoggerFactory.getLogger(SentinelService.class);
/**
* @SentinelResource: 可以理解就是一个资源名
*/
@SentinelResource("sentinelChain")
public String sentinelChain() {
logger.info("测试Sentinel流控模式 - 链路模式");
return "Sentinel Mode - Chain";
}
}
【b】控制层新增两个方法,都是调用上面的业务方法
@Autowired
private SentinelService sentinelService;
@GetMapping("/testA")
public String testA() {
log.info("SentinelController>>>>>testA() execute....");
return sentinelService.sentinelChain();
}
@GetMapping("/testB")
public String testB() {
log.info("SentinelController>>>>>testB() execute....");
return sentinelService.sentinelChain();
}
【c】Sentitel链路配置
下面我们就可以利用链路模式设置限制哪个入口的流量了,具体配置如下图:
注意:
从1.6.3 版本开始, Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效;
1.7.0 版本开始(对应Spring Cloud Alibaba的2.1.1.RELEASE),官方在CommonFilter 引入了WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context;将其配置为 false 即可根据不同的URL 进行链路限流;
上面这段是从百度down下来的,经过测试,SCA换成最新版本2.2.1.RELEASE仍然无效:配置spring.cloud.sentinel.web-context-unify=false无效!!!
推荐做法仍然是关闭官方的CommonFilter实例化,自己手动实例化CommonFilter,设置WEB_CONTEXT_UNIFY=fals
将SpringCloud Alibaba的版本调整2.1.1RELEASE
注意:最新版2.2.1RELEASE的CommonFilter类中,没有WEB_CONTEXT_UNIFY属性,此方法无效!!!
<spring-cloud-alibaba.version>2.1.1.RELEASE</spring-cloud-alibaba.version>
配置文件中关闭sentinel官方的CommonFilter实例化
spring:
cloud:
sentinel:
filter:
enabled: false
添加配置类,自己构建CommonFilter实例
package com.bruce;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
/**
* @BelongsProject: springcloud-alibaba-nacos
* @BelongsPackage: com.bruce
* @CreateTime: 2021-02-20 15:27
* @Description: TODO
*/
@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;
}
}
测试接口
浏览器分别对/testA和//testB进行频繁刷新访问,发现testB接口没有问题。而testA会有限流现象。
7.Sentinel流控效果 - 快速失败
快速失败也是默认的Sentinel流控效果,在前面的示例中已经介绍。
8.Sentinel流控效果 - 预热(Warm Up)
【a】限流、冷启动概述
当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。
这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等。
它的实现是在 Guava 的算法的基础上实现的。然而,和 Guava 的场景不同,Guava 的场景主要用于调节请求的间隔,即 Leaky Bucket,而 Sentinel 则主要用于控制每秒的 QPS,即我们满足每秒通过的 QPS 即可,我们不需要关注每个请求的间隔,换言之,我们更像一个 Token Bucket。
我们用桶里剩余的令牌来量化系统的使用率。假设系统每秒的处理能力为 b,系统每处理一个请求,就从桶中取走一个令牌;每秒这个令牌桶会自动掉落b个令牌。令牌桶越满,则说明系统的利用率越低;当令牌桶里的令牌高于某个阈值之后,我们称之为令牌桶"饱和"。
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
QPS阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值。默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
【b】WarmUp配置,具体配置如下图:
添加完成后如下图所示:
【c】测试
我们浏览器访问:http://localhost:8401/sentinel2,在访问开始的5秒钟之前,此时QPS还没达到阈值10,我们疯狂点击,因为sentinel还在预热阶段,所以访问结果如下:
可见,此时接口是会被sentinel限流的。
当过了5秒后,sentinel预热完成,QPS也恢复到阈值10,所以此时我们再次疯狂访问:http://localhost:8401/sentinel2时,接口是不会被sentinel限流的,当然,前提是一秒钟之内访问次数不能超过阈值10。
分析:因为我们设置的是QPS阈值是10,预热时长是5秒钟,系统初始化刚开始的时候阈值是10 / 3 = 约等于3,然后过了5秒钟后,系统阈值将会恢复升高到我们设置的阈值10。
应用场景:秒杀系统的开启瞬间,会有很多流量上来,很可能会把系统打挂,预热方式就是为了保护系统,可以慢慢的把流量放进来,慢慢的把阈值增长到设定的阈值。
9.entinel流控效果 - 排队等待
【a】概述
排队等待指的是严格控制请求通过的时间间隔,也即是让请求以均匀的速度通过,对应的是漏桶算法。
排队等待的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
【b】新增一个接口用于测试排队等待
@GetMapping("/sentinel3")
public String sentinel3() {
logger.info(Thread.currentThread().getName() + ">>>>>>sentinel3");
return "hello, sentinel dashboard....";
}
【c】排队等待配置,具体如下图
【d】借助postman循环访问/snetinel3这个资源
【e】测试结果
我们看看控制台的日志输出:
2021-02-20 16:07:19.809 INFO 8652 --- [ XNIO-1 task-18] com.bruce.controller.SentinelController : XNIO-1 task-18>>>>>>sentinel3
2021-02-20 16:07:20.128 INFO 8652 --- [ XNIO-1 task-19] com.bruce.controller.SentinelController : XNIO-1 task-19>>>>>>sentinel3
2021-02-20 16:07:20.445 INFO 8652 --- [ XNIO-1 task-20] com.bruce.controller.SentinelController : XNIO-1 task-20>>>>>>sentinel3
2021-02-20 16:07:20.767 INFO 8652 --- [ XNIO-1 task-21] com.bruce.controller.SentinelController : XNIO-1 task-21>>>>>>sentinel3
2021-02-20 16:07:21.094 INFO 8652 --- [ XNIO-1 task-22] com.bruce.controller.SentinelController : XNIO-1 task-22>>>>>>sentinel3
2021-02-20 16:07:21.412 INFO 8652 --- [ XNIO-1 task-23] com.bruce.controller.SentinelController : XNIO-1 task-23>>>>>>sentinel3
2021-02-20 16:07:21.731 INFO 8652 --- [ XNIO-1 task-24] com.bruce.controller.SentinelController : XNIO-1 task-24>>>>>>sentinel3
2021-02-20 16:07:22.047 INFO 8652 --- [ XNIO-1 task-25] com.bruce.controller.SentinelController : XNIO-1 task-25>>>>>>sentinel3
2021-02-20 16:07:22.371 INFO 8652 --- [ XNIO-1 task-26] com.bruce.controller.SentinelController : XNIO-1 task-26>>>>>>sentinel3
2021-02-20 16:07:22.696 INFO 8652 --- [ XNIO-1 task-27] com.bruce.controller.SentinelController : XNIO-1 task-27>>>>>>sentinel3
我们可以发现请求每隔1秒执行一次,这么多的请求没有被拒绝,而且进入的排队。
分析:因为我们设置的QPS阈值是1,表示一秒内只能有一个请求,但是我们Postman测试0.3秒发起一个请求,很明显超过阈值后,并没有触发sentinel限流,而是一个一个匀速执行。
排队的应用场景是什么呢?
比如有时候系统在某一个时刻会出现大流量,之后流量就恢复稳定,可以采用这种排队模式,大流量来时可以让流量请求先排队,等恢复了在慢慢进