流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
限流类型分为:
流控模式
流控效果
设置/hello
的QPS为1。
@GetMapping(value = "/hello")
public String hello() {
return "Hello Sentinel1";
}
这个比较好理解,直接快速访问hello即可。
正常的时候返回
Hello Sentinel1
如果访问太快,会返回如下,表示流量控制。
Blocked by Sentinel (flow limiting)
线程数限制,表示执行当前方法的线程数控制,因为访问太快,加入sleep,方便测试。
@GetMapping(value = "/hello")
public String hello() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello Sentinel1";
}
线程数设置1
使用jmeter进行测试。jmeter设置一秒2个请求。在View Results Tree中会有1个成功1个限流。
至于QPS限制,还是线程限制,需要不同的场景,进行选择。
设置/hello
的关联资源为/hello2
,表示如果hello2超过每秒1次的话,限制hello1。这个久很怪异,明明不管我的的事,却要限制我,但是存在即合理,应该也有这样的场景。
@GetMapping(value = "/hello")
public String hello() {
return "Hello Sentinel1";
}
@GetMapping(value = "/hello2")
public String hello2() {
return "Hello Sentinel2";
}
先通过jmeter对hello进行频繁访问,正常返回,没有测试出限流。再对hello2进行频繁访问,正常返回,没有限流。
验证关联控制。jmeter设置hello2为10秒请求30次,表示前10秒内hello返回限流。hello在这15秒请求15次,多5秒就是想测试没有了hello2的频繁访问,是否还限流。
设置信息如下。
hello2的返回信息一切正常。
hello前10秒限流,后5秒正常返回。
@Autowired
private TestService testService;
@GetMapping(value = "/hello")
public String hello() {
testService.listOrder();
return "Hello Sentinel1";
}
@GetMapping(value = "/hello2")
public String hello2() {
testService.listOrder();
return "Hello Sentinel2";
}
@Service
public class TestService {
//资源信息
@SentinelResource("listOrder")
public void listOrder(){
System.out.println("listOrder");
}
}
这样就形成了一个链路。一个controller对service调用的链路。在页面上有如下信息。
如此,配置即可限制/hello
对listOrder
的调用次数。当然在微服务之间也可以这么去设置。现在又对这个针对来源的概念模糊了,这两个不一样么?针对来源默认为default,,表示不区分来源,处理服务之间的限制,如果细化到方法的层面上,针对来源的设置无法做到,这时就需要进行链路设置。
流控效果中快速失败是最简单的限流处理策略,直接抛了个异常。
自定义失败
在被限流的时候抛出 FlowException
异常。FlowException
是 BlockException
的子类,您可以捕捉 BlockException
来自定义被限流之后的处理逻辑。
官方是这么说的,但是在直接对controller的接口进行限制的时候,通过spring全局异常捕获死活捕获不到。后来发现这里发现,这里进行controller的流量控制是通过Filter来控制的,所有spring的全局异常根本无法捕获。
@ControllerAdvice
@RestController
public class CommonException {
@ExceptionHandler(BlockException.class)
public String customException(Exception e) {
return "系统出小差,请稍后再试。";
}
@ExceptionHandler(UndeclaredThrowableException.class)
public String undeclaredThrowableException(Exception e) {
return "系统出小差,请稍后再试。";
}
@ExceptionHandler(Exception.class)
public String exception(Exception e) {
return "系统出小差,请稍后再试。";
}
}
如此,可以通过WebCallbackManager.setUrlBlockHandler
设置限流之后的处理类。
注意:Filter的具体实现在CommonFilter
中,有的版本通过拦截器实现,可以试着搜索AbstractSentinelInterceptor
,可能这种小改动不会影响什么,所以貌似官方没有说明。
@Configuration
public class Config {
static {
WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
// 简单演示,这里可以自行处理
PrintWriter out = response.getWriter();
out.print("try again later");
out.flush();
out.close();
}
});
}
}
当然,全局异常处理并不能解决所有的问题,更多的时候是希望每个方法都有自己的处理逻辑。
@SentinelResource(value = "helloResource" ,blockHandler = "getOrderDowngradeRtTypeFallback",fallback = "helloFallback")
@GetMapping(value = "/hello")
public String hello(@RequestParam(value = "id") Long id) {
if(id==1l){
throw new RuntimeException("1231");
}
return "SUCCESS";
}
public String helloFallback(Long id,Throwable e) {
System.out.println(1111);
return id+"helloFallback";
}
public String getOrderDowngradeRtTypeFallback(Long id,BlockException ex) {
return "服务降级啦,当前服务器请求次数过多,请稍后重试!";
}
通过SentinelResource注解来自定义异常的处理。
blockHandlerClass
效果一样。fallbackClass
指定处理类,效果是一样的。另外需要注意的是,如果添加SentinelResource
注解之后,在控制台会有如下信息。两个都可以对这个方法进行限制,但是/hello
是通过Filter实现的(后面改成了拦截器)全局异常可能是拦截不到的哦,具体可以看源码CommonFilter
或者AbstractSentinelInterceptor
,helloResource
是通过AOP来实现的,所以全局异常是可以对此进行处理的,源码SentinelResourceAspect
。
在使用之前版本的时候fallback 死活不好用,不知道是不是版本BUG。换成了这个版本就好用了, 也是在这个版本使用的时候,发现conroller限流从Filter变成了拦截器。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.1.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
这个是一个预热的概念,比如服务刚刚启动,所有的缓存还在加载中,不能有太多的请求进来,所以需要进行一个服务的预热,刚启动QPS限制小于设定数值,待服务启动一段时间后,慢慢的达到所设置的QPS。
如此设置,即可发现,开始的5秒中,QPS特别小,然后慢慢增加,到5秒的时候达到每秒10QPS。
设置超时时间后,如果QPS限制之后,会等待,超过设定时间后,抛出BlockException
异常。