Sentinel中的blockHandler和fallback以及规则持久化配置

热点规则

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel中的blockHandler和fallback以及规则持久化配置

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

实例

我们在模块cloudalibaba-sentinel-service8401的controller中新建方法:

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                            @RequestParam(value = "p2", required = false) String p2){
    return "testHotKey -----";
}

public String dealTestHotKey(String p1, String p2, BlockException blockException){
    return "dealTestHotKey---------";
}

它的含义是/testHotKey在sentinel中的资源名为testHotKey,如果被限流,则使用dealTestHotKey方法进行处理。

下面新建热点规则:

Sentinel中的blockHandler和fallback以及规则持久化配置

这里的参数索引对应的是方法中的参数索引,即p1,该配置的含义为,带p1参数的请求QPS不能超过1。否则会使用自己定义的dealTestHotKey方法进行处理。

Sentinel中的blockHandler和fallback以及规则持久化配置

参数例外项

如果热点key的取值为某个指定的取值,可以特殊地进行放行,将上面配置的热点规则进行编辑,选择高级选项,配置如下参数例外项:

Sentinel中的blockHandler和fallback以及规则持久化配置

该配置的含义为,如果p1的取值为5,则QPS的阈值可以提高至200。

系统自适应限流

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

Sentinel中的blockHandler和fallback以及规则持久化配置

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
  • 平均 RT(响应时间):当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
  • CPU 使用率(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

该功能的限流粒度为整个系统,需要谨慎配置。

SentinelResource注解

关于@SentinelResource,最基本的使用就是:

@RestController
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource(){
        return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));
    }

    public CommonResult handleException(BlockException blockException){
        return new CommonResult(444, blockException.getClass().getCanonicalName()+"\t服务不可用" );
    }
}

它定义了资源名和BlockException的处理器,这个自定义的处理器参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException

然而这样的使用存在着问题,例如没有默认的处理器,对每个方法都得配置,另外是把降级方法写进了controller,造成耦合。

自定义限流处理类

将限流处理的方法提出来可以解耦。

public class CustomerBlockHandler {

    public static CommonResult handlerException1(BlockException exception) {
        return new CommonResult(400, "客户自定义,global handlerException---1");
    }

    public static CommonResult handlerException2(BlockException exception) {
        return new CommonResult(444, "客户自定义,global handlerException---2");
    }
}

注意内部的处理方法必须是static的。

那么在controller中可以写:

@RestController
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException1")
    public CommonResult byResource(){
        return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));
    }
}

fallback和defaultFallback

blockHandler只能处理BlockException异常,即通过限流、熔断、降级配置的请求拦截抛出的异常,而对于服务本身抛出的异常不会处理,可以使用fallback函数来处理某个方法抛出的所有类型的异常(包括BlockException)

相关的@SentinelResource的三个属性:

  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。
  • exceptionsToIgnore:用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。这个属性的值是一个class对象的集合:exceptionsToIgnore={xxx.class,xxx.class}

规则持久化

sentinel中的服务如果一下线,配置过的规则都会消失,生产环境需要将配置规则进行持久化。

如何持久化?

不得不说这部分确实有点迷,官方对这部分的描述感觉这个功能不是很成熟?。

我是这样做的:

首先保证引入依赖(之前已经引入):

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

修改配置:(待会从nacos拉取配置)

server:
  port: 8401
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*'

因为要用push模式,大概原理是sentinel-dashboard直接把配置推到nacos上面,而sentinel客户端监听该配置的修改,动态获取该配置。

所以还得修改sentinel-dashboard,先去github上把sentinel的源码下下来,然后将
sentinel-dashboard\src\test\java\com\alibaba\csp\sentinel\dashboard\rule\nacos复制到sentinel-dashboard\src\main\java\com\alibaba\csp\sentinel\dashboard\rule下面。

修改sentinel-dashboard\下面的pom.xml文件,将

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <scope>test</scope>
</dependency>

中的<scope>test</scope>去掉。

然后修改entinel-dashboard\src\main\java\com\alibaba\csp\sentinel\dashboard\controller\v2下面的FlowControllerV2.java

@Autowired
// @Qualifier("flowRuleDefaultProvider")修改为
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
// @Qualifier("flowRuleDefaultPublisher")修改为
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

然后修改sentinel-dashboard\src\main\webapp\resources\app\scripts\directives\sidebar下面的sidebar.html文件:

<li ui-sref-active="active" ng-if="!entry.isGateway">
<!-- <a ui-sref="dashboard.flowV1({app: entry.app})"> 修改为-->
<a ui-sref="dashboard.flow({app: entry.app})">
    <i class="glyphicon glyphicon-filter"></i>  流控规则V2</a>
</li>

之后,进入sentinel-dashbord目录,重新打包:

mvn clean package

然后在target/目录下运行重新生成的包,试试看配置能不能持久化。

附上官方文档: https://github.com/alibaba/Sentinel/wiki/Sentinel-%E6%8E%A7%E5%88%B6%E5%8F%B0%EF%BC%88%E9%9B%86%E7%BE%A4%E6%B5%81%E6%8E%A7%E7%AE%A1%E7%90%86%EF%BC%89#%E8%A7%84%E5%88%99%E9%85%8D%E7%BD%AE

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/sentinel%e4%b8%ad%e7%9a%84blockhandler%e5%92%8cfallback%e4%bb%a5%e5%8f%8a%e8%a7%84%e5%88%99%e6%8c%81%e4%b9%85%e5%8c%96%e9%85%8d%e7%bd%ae/