@SentinelResource注解使用

之前我们仅依靠引入Spring Cloud Alibaba对Sentinel的整合封装spring-cloud-starter-alibaba-sentinel,就完成了对所有Spring MVC接口的限流控制。然而,在实际应用过程中,我们可能需要限流的层面不仅限于接口。可能对于某个方法的调用限流,对于某个外部资源的调用限流等都希望做到控制。

对此,我们需要学习使用@SentinelResource注解,灵活的定义控制资源以及如何配置控制策略。

自定义资源点

1、导入sentinel依赖:

  <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>   

第一步:在应用主类中增加注解支持的配置:

@SpringBootApplication
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
    // 注解支持的配置Bean
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

第二步:在需要通过Sentinel来控制流量的地方使用@SentinelResource注解,比如下面以控制Service逻辑层的某个方法为例:

@Slf4j
@Service
public class TestService {
    @SentinelResource(value = "doSomeThing")
    public void doSomeThing(String str) {
        log.info(str);
    }
}

到这里一个需要被保护的方法就定义完成了。

如何实现限流与熔断降级

在定义了资源点之后,我们就可以通 过Dashboard来设置限流和降级策略来对资源点进行保护了。同时,也可以通过@SentinelResource来指定出现限流和降级时候的异常处理策略。

实现限流控制

第一步:在Web层调用这个被保护的方法:

@RestController
public class TestController {

    @Autowired
    private TestService testService;

    @GetMapping("/hello")
    public String hello() {
        estService.doSomeThing("hello " + new Date());
        return "didispace.com";
    }

}

第二步:启动测试应用,启动Sentinel-Dashboard。发一个请求到/hello接口上,使得Sentinel-Dashboard上可以看到如下图所示的几个控制点:
在这里插入图片描述
可以看到,除了有/hello资源点之外,多了一个doSomeThing资源点。可以通过界面为这个资源点设置限流规则,比如将其QPS设置为2。由于/hello资源不设置限流规则,所以只要请求/hello接口,就可以直接模拟调用doSomeThing资源,来观察限流规则是否生效。
在这里插入图片描述
此时,服务端的控制台也会有对应的限流报错日志:

INFO 36898 --- [nio-8001-exec-3] c.d.a.sentinel.service.TestService       : aaa
ERROR 36898 --- [nio-8001-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.flow.FlowException: null

实现限流的异常处理

默认情况下,Sentinel对控制资源的限流处理是直接抛出异常【即上面日志抛出的异常】。在没有合理的业务承接或者前端对接情况下可以这样,但是正常情况为了更好的用户业务,都会实现一些被限流之后的特殊处理,我们不希望展示一个生硬的报错。那么只需要基于上面的例子做一些加工,比如:

@Slf4j
@Service
public class TestService {
    @SentinelResource(value = "doSomeThing", blockHandler = "exceptionHandler")
    public void doSomeThing(String str) {
        log.info(str);
    }
    // 限流与阻塞处理
    public void exceptionHandler(String str, BlockException ex) {
        log.error( "blockHandler:" + str, ex);
    }    
}

主要做了两件事:

  • 通过@SentinelResource注解的blockHandler属性制定具体的处理函数
  • 实现处理函数,该函数的传参必须与资源点的传参一样,并且最后加上BlockException异常参数;同时,返回类型也必须一样

此时前端就不会返回异常信息了,后端会打印exceptionHandler中定义的日志输出。

实现熔断降级

@SentinelResource注解除了可以用来做限流控制之外,还能实现与Hystrix类似的熔断降级策略。下面就来具体看看如何使用吧。

什么是熔断降级
除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。这个问题和 Hystrix 里面描述的问题是一样的。
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix: 线程池隔离
Sentinel:通过并发线程数进行限制、通过响应时间对资源进行降级

第一步:与限流控制一样,使用@SentinelResource注解标记资源点,比如:

@Slf4j
@Service
public class TestService {
    @SentinelResource(value = "doSomeThing2")
    public void doSomeThing2(String str) {
        log.info(str);
        throw new RuntimeException("发生异常");
    }
}

这里在TestService类中创建了一个新的方法,并使用@SentinelResource将该资源命名为doSomeThing2。该方法会抛出异常,以配合后续制定基于异常比例的降级策略(类似Hystrix)。Sentinel相比Hystrix更丰富,还有基于响应时间和异常数的降级策略。

第二步:在Web层调用这个被保护的方法:

@RestController
public class TestController {

    @Autowired
    private TestService testService;

    @GetMapping("/hello2")
    public String hello2() {
        testService.doSomeThing2("hello2 " + new Date());
        return "didispace.com";
    }

}

第三步:启动测试应用,启动Sentinel-Dashboard。发一个请求到/hello2接口上,使得Sentinel-Dashboard上可以看到名为doSomeThing2的资源点。然后点击”降级“按钮,为该资源设置降级规则。这里使用异常比例策略,比例设置为0.5(即:50%的异常率),时间窗口设置为2(秒)。在这里插入图片描述

第四步:验证熔断降级,根据上面的降级策略配置,当doSomeThing2方法的调用QPS >= 5,如果异常率超过50%,那么后续2秒内的调用将直接出发熔断降级,默认情况会直接抛出DegradeException异常,比如:

ERROR 99863 --- [nio-8001-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.degrade.DegradeException: null

与限流相比,限流抛出的异常是com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
而熔断降级抛出的异常是com.alibaba.csp.sentinel.slots.block.degrade.DegradeException: null

熔断的降级处理

Sentinel中定义熔断的降级处理方法非常简单,与Hystrix非常相似。只需要使用@SentinelResource注解的fallback属性来指定具体的方法名即可。这里也需要注意传参与返回必须一致。比如:

@Slf4j
@Service
public class TestService {
    // 熔断与降级处理
    @SentinelResource(value = "doSomeThing2", fallback = "fallbackHandler")
    public void doSomeThing2(String str) {
        log.info(str);
        throw new RuntimeException("发生异常");
    }
    public void fallbackHandler(String str) {
        log.error("fallbackHandler:" + str);
    }
}

完成上面的改造之后,重启应用,并设置doSomeThing2资源的熔断降级策略(使用异常百分比),然后频繁的请求/hello2接口。在QPS>=5之后,由于这个接口一直在抛出异常,所以一定会满足熔断降级条件,这时候就会执行fallbackHandler方法,不断的打印如下日志:

ERROR 58471 --- [nio-8001-exec-1] c.d.a.sentinel.service.TestService       : fallbackHandler:hello2 Thu Jun 27 23:44:19 CST 2019
ERROR 58471 --- [nio-8001-exec-2] c.d.a.sentinel.service.TestService       : fallbackHandler:hello2 Thu Jun 27 23:44:19 CST 2019
ERROR 58471 --- [nio-8001-exec-3] c.d.a.sentinel.service.TestService       : fallbackHandler:hello2 Thu Jun 27 23:44:19 CST 2019
ERROR 58471 --- [nio-8001-exec-4] c.d.a.sentinel.service.TestService       : fallbackHandler:hello2 Thu Jun 27 23:44:19 CST 2019
ERROR 58471 --- [nio-8001-exec-5] c.d.a.sentinel.service.TestService       : fallbackHandler:hello2 Thu Jun 27 23:44:20 CST 2019

@SentinelResource注解 更多属性说明:

  • value资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException
    blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallbackfallback 函数名称,可选项,用于在抛出异常的时候提供 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 处理逻辑。若未配置 blockHandlerfallback defaultFallback,则被限流降级时会将 BlockException 直接抛出。

Sentinel 配置项

application.yaml 配置文件中Sentinel 配置项:
配置文件如下:

spring:
  application:
    name: demo-provider
  cloud:
    # Sentinel 配置项,对应 SentinelProperties 配置属性类
    sentinel:
      enabled: true # 是否开启。默认为 true 开启
      eager: true # 是否饥饿加载。默认为 false 关闭
      transport:
        dashboard: 127.0.0.1:7070 # Sentinel 控制台地址
      filter:
        url-patterns: /** # 拦截请求的地址。默认为 /*      

Sentinel 配置项,以 spring.cloud.sentinel 开头,对应SentinelProperties配置属性类。

  • enabled 配置项,设置是否开启 Sentinel,默认为 true 开启,所以一般不用主动设置。如果胖友关闭 Sentinel 的功能,例如说在本地开发的时候,可以设置为 false 关闭。
  • eager 配置项,设置是否饥饿加载,默认为 false 关闭。默认情况下,Sentinel 是延迟初始化,在首次使用到 Sentinel 才进行初始化。通过设置为 true 时,在项目启动时就会将 Sentinel 直接初始化,完成向 Sentinel 控制台进行注册。
  • transport.dashboard 配置项,设置 Sentinel 控制台地址。
  • filter.url-patterns 配置项,设置拦截请求的地址,默认为 /*

在 Sentinel 的子项目 sentinel-spring-webmvc-adapter 中,对 SpringMVC 进行适配,通过 SentinelWebInterceptor 拦截器,实现对 SpringMVC 的请求的拦截,使用 Sentinel 进行保护。通过 filter.url-patterns 配置项,可以定义该拦截器的拦截请求地址。
不过要注意,因为 filter.url-patterns 配置项的默认值为 /*,只能拦截根目录的请求,显然不满足我们的日常需求,因此修改成了 /** 拦截所有请求

BlockException 处理器

BlockException 是一个异常抽象基类,其有 5 个实现类,刚好对应 Sentinel 的 5 种流量控制手段,如下图所示:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值