如何在项目中添加Sentinel的支持请看这里
Sentinel笔记(一)第一个监控实例
这个没啥东西就放在这个位置了,除了自适应和CPU占用率,都和之前的限流规则一样,只不过是使用范围覆盖了整个服务的所有资源
官方文档
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
官方文档
先来看一下官方文档的介绍
注意:注解方式埋点不支持 private 方法。
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
- value:资源名称,必需项(不能为空)
- entryType:entry 类型,可选项(默认为 EntryType.OUT)
- blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。
看一下这个注解的源码会发现,该注解只能用在方法上,这是肯定的,因为注解中的属性基本都是针对单个方法的
还有文档中说不能为空的value其实给了默认值的
如果value不赋值,在控制台查看的时候就会变成下面这个样子,资源名称就是方法的全路径,文档说的不能为空应该是为了在控制台中配置时的可读性而来考虑的
在方法上添加了该属性就可以指定该方法的资源名称为 getOrder
控制台中的效果如图,然后就可以对该资源进行各种限流熔断降级等配置了
在controller中不添加该注解,Sentinel会将所有访问到本服务的资源添加到控制台,包括一个不存在的资源,不知道这个问题能否通过gateway来解决,有时间再研究这块把
blockHandler指定了请求不符合控制台规则后出现的降级所要调用的方法,不会处理程序运行时出现内部异常的情况
官方文档中写明了使用 blockHandler 时需要遵守的约束
blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中(后面使用blockHandlerClass可以定义到其他类中)。
创建 testBlock() 接口
@GetMapping("/test/block")
@SentinelResource(value = "testBlock",blockHandler = "test_block")
public String testBlock(@RequestParam(value ="p1", required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "=======test Block======";
}
public String test_block(String p1, String p2, BlockException e){
return "=======test_block======";
}
添加流控或者降级规则,这里添加是流控规则 QPS为1
注意资源名一定要是 @SentinelResource 注解中 value 的值,如果使用 url 来指定,那么 @SentinelResource 注解不会生效
疯狂刷新 http://localhost:8401/test/block
地址,成功降级
我们当然不希望降级方法写在controller中,这样会造成代码膨胀,耦合度过高等问题
所以我们可以使用 blockHandlerClass 来指定降级方法在哪个外部类
除了blockHandler的规定外,使用 blockHandlerClass 还有以下规定
若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
创建 CustomerBlockHandler 类,用于存放外部的方法,test_block() 方法挪到这里来,并且声明为静态方法
public class CustomerBlockHandler {
public static String test_block(String p1, String p2, BlockException e){
return "=======CustomerBlockHandler_test_block======";
}
}
修改testBlock()方法
@GetMapping("/test/block")
@SentinelResource(value = "testBlock",blockHandler = "test_block",blockHandlerClass = CustomerBlockHandler.class)
public String testBlock(@RequestParam(value ="p1", required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "=======test Block======";
}
重启服务,由于我们没有做Sentinel的持久化,所以重新创建一遍上面的限流规则
疯狂访问:http://localhost:8401/test/block
成功降级
fallback指定了内部出现异常后降级调用给的方法,不会处理不符合控制台规则而发生的降级
比如下面的例子,当访问 http://localhost:8401/test/fallback?p1=111
时,就会执行下面的 test_Fallback() 方法来返回结果
官方文档中说明了该fallback指定的方法有以下限定,注意这里参数中可以添加的异常类型和blockHandler 的并不一样
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- fallback 函数默认需要和原方法在同一个类中。(下面fallbackClass可以打破这个默认设置)
@GetMapping("/test/fallback")
@SentinelResource(value = "testFallback", fallback = "test_Fallback")
public String testFallback(@RequestParam(value ="p1", required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
if(p1.equals("111")){
throw new IllegalArgumentException("aaaa");
}
return "=======testFallBack======";
}
public String test_Fallback(String p1, String p2, Throwable e){
return "=======test_fallBack======";
}
结果:
当然这种书写方式会导致我们的controller中代码爆炸式的增长,所以我们更希望将降级方法写到其他地方
除了上面 fallback 的限定,fallbackClass 还有以下限定
- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
思考:如果不用static修饰成静态方法,我们又该在哪里实例化 fallbackClass 指定的类呢?显然静态方法就不用考虑这个问题了
定义类(随便起名) CustomerFallbackHandler
public class CustomerFallbackHandler {
public static String test_Fallback(String p1, String p2, Throwable e){
return "=======CustomerFallbackHandler_test_fallBack======";
}
}
修改 testFallback() 方法
@GetMapping("/test/fallback")
@SentinelResource(value = "testFallback", fallback = "test_Fallback",fallbackClass = CustomerFallbackHandler.class)
public String testFallback(@RequestParam(value ="p1", required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
if(p1.equals("111")){
throw new IllegalArgumentException("aaaa");
}
return "=======testFallBack======";
}
再次访问 : http://localhost:8401/test/fallback?p1=111
,和预期结果一样
从1.6.0之后的版本支持了 defaultFallback ,与fallbackHandler的使用方法无异,只不过是可以在通用的 fallback 上,不再详述
exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
该属性指定哪些类型的异常不会走到 fallback 方法中,该属性是一个Class类型的数组,泛型类型必须继承自 Throwable
Class<? extends Throwable>[] exceptionsToIgnore() default {};
下面来测试一下
修改testFallback() 方法,在 @SentinelResource 注解中添加
exceptionsToIgnore = {IllegalArgumentException.class}
意思是抛出IllegalArgumentException
异常时原样抛出,不会走到 fallback 逻辑中
@GetMapping("/test/fallback")
@SentinelResource(value = "testFallback",
fallback = "test_Fallback",
fallbackClass = CustomerFallbackHandler.class,
exceptionsToIgnore = {IllegalArgumentException.class})
public String testFallback(@RequestParam(value ="p1", required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
if(p1.equals("111")){
throw new IllegalArgumentException("aaaa");
}
return "=======testFallBack======";
}
重启服务测试下
访问http://localhost:8401/test/fallback?p1=111
可以看到我们定义的异常被原样抛出
因为接口内有这么一句代码 p1.equals("111")
,所以不传 p1 的话肯定会报空指针异常
那么再访问下http://localhost:8401/test/fallback
,可以看到空指针异常正确走到fallback的逻辑