SpringCloudAlibaba微服务实战系列(四)Sentinel熔断降级、异常fallback、block细致处理

SpringCloudAlibaba Sentinel降级和熔断

接着上篇文章的内容,在Sentinel中如何进行降级和熔断呢?

熔断降级规则

降级规则

在Sentinel中降级主要有三个策略:RT、异常比例、异常数,也是针对某个资源的设置。而在1.8.0+版本后RT改为了慢调用比例

需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

RT:表示该资源1s内处理请求的平均响应时间。

注意:RT值的上限时4900ms,及时超过也是4900ms,如需自定义,可以在启动sentinel时增加参数

shell 复制代码
-Dcsp.sentinel.statistic.max.rt=x

慢调用比例

依旧是在簇点链路的列表视图选择/sentinelTest一行,进入熔断,设置参数如图:

RT设置为800ms,熔断时长设置为20s,为了测试效果,把接口睡眠1s。

java 复制代码
@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%

表示请求该资源的异常总数占比。先模拟一个异常

java 复制代码
@RequestMapping("/sentinelTest")
public String sentinelTest() {
    int i = 1 / 0;	// 除数为0
    return "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处理。(注意:这里是在服务提供者方设置的

java 复制代码
@Component
public class CustomRequestOriginParser implements RequestOriginParser {
    @Override
    public 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时仍会被拒绝,而其他的值则是可以访问

shell 复制代码
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。

java 复制代码
@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,那么设置流控也是针对这个资源设置,让后面的请求进入我们自定义的处理中。

可以看到,流控超过阈值后,其他的所有请求都是走的我们自定义的处理器。

热点规则

在一段时间内访问很频繁的资源是热点资源,需要针对资源做参数化定制。

java 复制代码
@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之前的版本是不支持针对业务异常处理的

java 复制代码
@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

java 复制代码
@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定义的方法可以针对所有类型的异常,我们也可以忽略某些异常。

java 复制代码
@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中有blockHandlerClassfallbackClass。顾名思义,blockHandlerClass中写blockHandler函数,fallbackClass中写fallback的函数。

java 复制代码
// 异常fallback
public class ExceptionHandler {
    public static String sentinelTestFallback(String params) {
        return "testCon#sentinelTestFallback" + RandomUtils.nextInt(0, 1000);
    }
}
java 复制代码
// blockHandler处理
public class BlockHandler {
    public static String sentinelBlock(String params, BlockException e) {
        return "testCon#sentinelBlock" + RandomUtils.nextInt(0, 1000);
    }
}

两个类中的方法必须是static 修饰的,且参数要和原方法保持一致,否则无法解析噢

接口原方法

java 复制代码
@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);
}
相关推荐
fanly113 天前
Surging AI Agent 完整产品介绍
微服务·microservice
蝎子莱莱爱打怪9 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking10 天前
Java微服务练习方式
java·后端·微服务
米丘13 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质16 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
霸道流氓气质16 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
地瓜伯伯16 天前
从MESI缓存一致性协议讲透synchronized的底层
java·spring boot·spring·spring cloud·微服务·springcloud
Devin~Y16 天前
大厂 Java 面试实录:从音视频内容社区到 AI RAG 的全链路技术设计
java·spring boot·redis·spring cloud·微服务·kafka·音视频
递归尽头是星辰16 天前
AI 访问数据仓库:从直连到微服务化
数据仓库·人工智能·微服务·dataagent·ai数据治理
就改了17 天前
Windows 环境 SkyWalking 完整实操教程
windows·微服务·skywalking