SpringCloudAlibaba Sentinel降级和熔断
接着上篇文章的内容,在Sentinel中如何进行降级和熔断呢?
熔断降级规则
降级规则
在Sentinel中降级主要有三个策略:RT、异常比例、异常数,也是针对某个资源的设置。而在1.8.0+
版本后RT改为了慢调用比例
需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
RT:表示该资源1s内处理请求的平均响应时间。
注意:RT值的上限时4900ms,及时超过也是4900ms,如需自定义,可以在启动sentinel时增加参数
-Dcsp.sentinel.statistic.max.rt=x
慢调用比例
依旧是在簇点链路的列表视图选择/sentinelTest
一行,进入熔断,设置参数如图:
RT设置为800ms,熔断时长设置为20s,为了测试效果,把接口睡眠1s。
@RequestMapping("/sentinelTest")
public String sentinelTest() throws InterruptedException {Thread.sleep(1000);return "sentinel-consumer9001 sentinelTest" + RandomUtils.nextInt(0, 1000);
}
解读:响应时间超过RT值的请求被称为慢调用。在单位时间(上图的统计时长1s)内,请求的数量大于最小请求数(5)
,且慢调用的比例>=阈值,此资源进入熔断状态(20s内不可用)。
Jmeter请求/sentinelTest
,使用10个线程执行100次结果。
前面几个请求是正常返回数据,后面全部降级处理,直接返回提示信息(此时该资源已经进入了熔断状态,可以理解为家里的电闸给关了,必须重新打开电闸,才能恢复使用电力
)。后面这个资源无论怎样被调用,都无法进入接口,直接返回提示。
异常比例
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%
表示请求该资源的异常总数占比。先模拟一个异常
@RequestMapping("/sentinelTest")
public String sentinelTest() {int i = 1 / 0; // 除数为0return "sentinel-consumer9001 sentinelTest" + RandomUtils.nextInt(0, 1000);
}
设置规则
解读:当1s内,请求数量>5,且异常的比例大于80%,熔断20s
调用资源
前几个请求正常请求返回异常提示,而后面的所有请求直接被拒绝访问。
异常数
该资源近1分钟内的异常数量。
解读:当1s内,请求数量>5,且异常的数量>=10,熔断20s
经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
当到11个请求仍然是异常时,直接熔断。
系统规则
之前的所有规则都是针对某个资源(接口)而言的,后面我们将针对整个应用设置系统规则。相对更加粗粒度,属于应用级别的入口流量控制。那么相对应的也有几种规则:
- LOAD:负载,当系统负载超过设定值,且发现线程数超过预估系统容量就会触发保护机制。
- RT:整个应用上所有资源平均的响应时间,而不是固定某个资源
- 线程数:设定整个系统所能使用的业务线程数阈值,不固定某个资源
- 入口QPS:整个应用所有的每秒处理的请求数
- CPU使用率:这个应用占用的CPU的百分比
使用时可以根据服务器的情况设置即可。
授权规则
授权规则是根据调用方判断调用资源的请求是否应该被允许访问。Sentinel提供了黑白名单的授权类型,白名单表示允许调用资源,黑名单则不允许调用资源。
在java中实现相关的接口,将返回值交给sentinel处理。(注意:这里是在服务提供者方设置的
)
@Component
public class CustomRequestOriginParser implements RequestOriginParser {@Overridepublic String parseOrigin(HttpServletRequest httpServletRequest) {String origin = httpServletRequest.getParameter("origin"); // 区分来源,本质通过request域获取来源标识if (StringUtils.isEmpty(origin)) {throw new RuntimeException("origin不能为空");}return origin; // 将返回的结果交给sentinel处理}
}
然后配置个授权规则
资源名/test
设置app为黑名单
当请求provider服务上的接口时,若origin为空则会被拒绝访问,若origin=app时仍会被拒绝,而其他的值则是可以访问
D:\springcloud\doc>curl localhost:8002/test?origin=app
==>Blocked by Sentinel (flow limiting)
D:\springcloud\doc>curl localhost:8002/test?origin=pc
==>sentinel-provider8002 test()921
使用@SentinelResource注解
之前主要是利用Sentinel仪表板控制一些参数保护应用。后面我们使用@SentinelResource注解根据实际情况实现定制化功能,对应用的保护更加细粒度。
现在限制达到阈值时,直接提示Blocked by Sentinel(flow limiting)
,提示不太友好,需要实现更精细化的控制。
blockHandler属性–负责响应控制面板配置
添加一个接口/blockHandlerTest
资源名为blockHandlerTest,如果违反Sentinel控制台的规则,则进入blockHandlerTestHander。
@RequestMapping("/blockHandlerTest")
@SentinelResource(value = "blockHandlerTest", blockHandler = "blockHandlerTestHandler")
public String blockHandlerTest(String params) {return "Test#blockHandlerTest" + RandomUtils.nextInt(0, 1000);
}public String blockHandlerTestHandler(String params, BlockException bl) {return "Test#blockHandlerTest" + RandomUtils.nextInt(0, 1000) + bl.getMessage();
}
注意:blockHandlerTestHandler方法的返回值要和原方法一致,并且除了原有的参数,还要带上BlockException的参数
设置一个流控,在@SentinelResouce注解中我们把资源名设置为blockHandlerTest
,那么设置流控也是针对这个资源
设置,让后面的请求进入我们自定义的处理中。
可以看到,流控超过阈值后,其他的所有请求都是走的我们自定义的处理器。
热点规则
在一段时间内访问很频繁的资源是热点资源,需要针对资源做参数化定制。
@RequestMapping("/testHotKeyA")
@SentinelResource(value = "testHotKeyA", blockHandler = "blockTestHotKeyA")
public String testHotKeyA(@RequestParam(value = "orderId", required = false) String orderId,@RequestParam(value = "userId", required = false) String userId) {return "Test#testHotKeyA" + RandomUtils.nextInt(0, 1000);
}public String blockTestHotKeyA(String orderId, String userId, BlockException bl) {return "Test#blockTestHotKeyA" + RandomUtils.nextInt(0, 1000) + bl.getMessage();
}
去sentinel页面上加一个热点key规则
索引从0开始,那么获取的就是我们的orderId
参数,在调用/testHotKeyA
时要加上oderId
参数否则不生效。
正确进入处理。
同时,我们可以对热点资源具体的某个参数值
做阈值限制。
上图是对orderId
为111或222时,阈值设置为500.再次测试,基本上不会进入自定义的处理中。但是为其他值时还是会进入我们的自定义处理。
fallback处理
前面是针对违反sentinel控制台规则做的处理,那么当我们的业务层面出现问题时,要做异常回滚等,则要使用fallback处理。同样是@SentinelResource中的属性。sentinel-1.6.0之前的版本是不支持针对业务异常处理的
@RequestMapping("/fallbackTest")
@SentinelResource(value = "fallbackTest", fallback = "fallbackHandler")
public String fallbackTest(String params) {int i = 1 / 0;return "Test#fallbackTest" + RandomUtils.nextInt(0, 1000);
}public String fallbackHandler(String params) {return "Test#fallbackHandler" + RandomUtils.nextInt(0, 1000);
}
所有的请求都进入到异常处理的方法中了。
fallback+blockHandler
@RequestMapping("/sentinelUnionTest")
@SentinelResource(value = "sentinelUnionTest", fallback = "sentinelUnionTestFallback", blockHandler = "sentinelUnionTestBlockHandler")
public String sentinelUnionTest(String params) {int i = 1 / 0;return "Test#fallbackTest" + RandomUtils.nextInt(0, 1000);
}public String sentinelUnionTestFallback(String params) {return "Test#sentinelUnionTestFallback" + RandomUtils.nextInt(0, 1000);
}public String sentinelUnionTestBlockHandler(String params, BlockException bl) {return "Test#sentinelUnionTestBlockHandler" + RandomUtils.nextInt(0, 1000) + bl.getMessage();
}
对sentinelUnionTest
资源设置流控,调用接口观察结果
第一个接口是正常进入到了fallback处理,然后后面的请求因为超过阈值,直接进入block处理中了。
忽略异常–exceptionsToIngnore
fallback定义的方法可以针对所有类型的异常,我们也可以忽略某些异常。
@RequestMapping("/fallbackTest")
@SentinelResource(value = "fallbackTest", fallback = "fallbackHandler", exceptionsToIgnore = ArithmeticException.class)
public String fallbackTest(String params) {int i = 1 / 0;return "Test#fallbackTest" + RandomUtils.nextInt(0, 1000);
}public String fallbackHandler(String params) {return "Test#fallbackHandler" + RandomUtils.nextInt(0, 1000);
}
模拟了一个计算异常,但是此异常被忽略了,所以不会进入到fallbackHandler中进行处理,而是直接jvm抛异常给客户端响应。
代码优化
前面的代码中都是把fallback和block全都写在了一起,这样是不符合程序单一性原则的,毕竟controller层有很多之外的逻辑,二来别的类也不好复用。
sentinel考虑到这些情况,在@SentinelResource中有blockHandlerClass
和fallbackClass
。顾名思义,blockHandlerClass中写blockHandler函数,fallbackClass中写fallback的函数。
// 异常fallback
public class ExceptionHandler {public static String sentinelTestFallback(String params) {return "testCon#sentinelTestFallback" + RandomUtils.nextInt(0, 1000);}
}
// blockHandler处理
public class BlockHandler {public static String sentinelBlock(String params, BlockException e) {return "testCon#sentinelBlock" + RandomUtils.nextInt(0, 1000);}
}
两个类中的方法必须是static 修饰的,且参数要和原方法保持一致,否则无法解析噢
接口原方法
@RequestMapping("/sentinelUnionTest")
@SentinelResource(value = "sentinelUnionTest",fallbackClass = ExceptionHandler.class, fallback = "sentinelTestFallback", // 指定类和方法名blockHandlerClass = BlockHandler.class, blockHandler = "sentinelBlock") // 指定类和方法名
public String sentinelUnionTest(String params) {int i = 1 / 0;return "Test#fallbackTest" + RandomUtils.nextInt(0, 1000);
}