一、关于

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

Sentinel有以下两个基本概念:

  • 资源:资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源

  • 规则:围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整

本文代码仓库地址:https://github.com/lazyrabb1t/rabb-springcloud-demo

二、主要功能

2.1 流量控制

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

  • resource:资源名,即限流规则的作用对象
  • count: 限流阈值
  • grade: 限流阈值类型(QPS 或并发线程数)
  • limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
  • strategy: 调用关系限流策略
  • controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
2.1.1 并发线程数控制

并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。

2.1.2 QPS流量控制

当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:直接拒绝、Warm Up、匀速排队。若使用除了直接拒绝之外的流量控制效果,则调用关系限流策略(strategy)会被忽略。

  • 直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。

  • Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

  • 匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。

2.1.3 基于调用关系的流量控制

调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系。Sentinel 通过 NodeSelectorSlot 建立不同资源间的调用的关系,并且通过 ClusterBuilderSlot 记录每个资源的实时统计信息。

有了调用链路的统计信息,我们可以衍生出多种流量控制手段。

1)根据调用方限流

流控规则中的 limitApp 字段用于根据调用来源进行流量控制。该字段的值有以下三种选项,分别对应不同的场景:

  • default:表示不区分调用者,来自任何调用者的请求都将进行限流统计。如果这个资源名的调用总和超过了这条规则定义的阈值,则触发限流。
  • {some_origin_name}:表示针对特定的调用者,只有来自这个调用者的请求才会进行流量控制。例如 NodeA 配置了一条针对调用者caller1的规则,那么当且仅当来自 caller1 对 NodeA 的请求才会触发流量控制。
  • other:表示针对除 {some_origin_name} 以外的其余调用方的流量进行流量控制。例如,资源NodeA配置了一条针对调用者 caller1 的限流规则,同时又配置了一条调用者为 other 的规则,那么任意来自非 caller1 对 NodeA 的调用,都不能超过 other 这条规则定义的阈值。
    同一个资源名可以配置多条规则,规则的生效顺序为:{some_origin_name} > other > default

2)根据调用链路入口限流:链路限流

资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。

一棵典型的调用树如下图所示:

                   machine-root
                    /       \
                   /         \
             Entrance1     Entrance2
                /             \
               /               \
      DefaultNode(nodeA)   DefaultNode(nodeA)

上图中来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 strategy 为 RuleConstant.STRATEGY_CHAIN,同时设置 refResource 为 Entrance1 来表示只有从入口 Entrance1 的调用才会记录到 NodeA 的限流统计当中,而不关心经 Entrance2 到来的调用。

3)具有关系的资源流量控制:关联流量控制

当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。

2.2 集群流控

为什么要使用集群流控呢?假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有 100 台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用。这就是最基础的集群流控的方式。

另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。

2.3 熔断降级

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

2.3.1 熔断策略

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
2.3.2 熔断降级规则说明

熔断降级规则(DegradeRule)包含下面几个重要的属性:

Field 说明 默认值
resource 资源名,即规则的作用对象
grade 熔断策略,支持慢调用比例/异常比例/异常数策略 慢调用比例
count 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow 熔断时长,单位为 s
minRequestAmount 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) 5
statIntervalMs 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) 1000 ms
slowRatioThreshold 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
2.3.3 熔断器事件监听

Sentinel 支持注册自定义的事件监听器监听熔断器状态变换事件(state change event)。示例:

EventObserverRegistry.getInstance().addStateChangeObserver("logging",
    (prevState, newState, rule, snapshotValue) -> {
        if (newState == State.OPEN) {
            // 变换至 OPEN state 时会携带触发时的值
            System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
                TimeUtil.currentTimeMillis(), snapshotValue));
        } else {
            System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(),
                TimeUtil.currentTimeMillis()));
        }
    });

2.4 热点参数限流

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

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
    热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

热点参数规则(ParamFlowRule)类似于流量控制规则(FlowRule):

属性 说明 默认值
resource 资源名,必填
count 限流阈值,必填
grade 限流模式 QPS 模式
durationInSec 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 1s
controlBehavior 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 快速失败
maxQueueingTimeMs 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 0ms
paramIdx 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置
paramFlowItemList 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型
clusterMode 是否是集群参数流控规则 false
clusterConfig 集群流控相关配置

2.5 系统自适应限流

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

在开始之前,我们先了解一下系统保护的目的:

  • 保证系统不被拖垮
  • 在系统稳定的前提下,保持系统的吞吐量

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

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

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

2.6 黑白名单控制

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

三、使用

3.1 部署Sentinel控制台

3.1.1 获取 Sentinel 控制台

下载最新版本的控制台 jar 包:

https://github.com/alibaba/Sentinel/releases

3.1.2 启动

使用如下命令启动控制台:

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

其中 -Dserver.port=8080 用于指定 Sentinel 控制台端口为 8080。

从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。可以通过如下参数进行配置:

  • -Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel;
  • -Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel;
  • -Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟;
3.1.3 访问控制台

浏览器访问:http://localhost:8080

3.2 创建sentinel客户端工程

3.2.1 添加依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
3.2.2 配置
server:
  port: 20300
spring:
  application:
    name: rabb-sentinel
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8880
3.2.3 @SentinelResource注解

@SentinelResource 注解用来标识资源是否被限流、降级。

@SentinelResource 还提供了其它额外的属性如 blockHandler,blockHandlerClass,fallback 用于表示限流或降级的操作(注意有方法签名要求)。有两种方法进行配置

  • 使用blockHandlerClass定义外部类,使用blockHandler定义的静态方法,fallback类似
  • 直接使用blockHandler,在本类中寻找对应方法,fallback类似

若不配置 blockHandler、fallback 等函数,则被流控降级时方法会直接抛出对应的 BlockException;若方法未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException。

一般推荐将 @SentinelResource 注解加到服务实现上,而在 Web 层直接使用 Spring Cloud Alibaba 自带的 Web 埋点适配。Sentinel Web 适配同样支持配置自定义流控处理逻辑。

3.2.4 创建测试接口以及业务类

定义外部的异常处理方法以及熔断回调方法:

public class SentinelConfig {

    public static String blockHandler(BlockException exception) {
        return "Exception: " + exception.getMessage();
    }

    public static String fallback() {
        return "Fallback";
    }

}

业务逻辑:

@Service
public class TestService {

    /**
     * blockHandler 流量控制后会进入该方法
     * fallback 熔断后进入该方法
     * 两种方法进行配置
     * 1、使用blockHandlerClass定义外部类,使用blockHandler定义的静态方法,fallback类似
     * 2、直接使用blockHandler,在本类中寻找对应方法,fallback类似
     */
    @SentinelResource(value = "doSomething", blockHandlerClass = SentinelConfig.class, blockHandler = "blockHandler",
            fallbackClass = SentinelConfig.class, fallback = "fallback")
    public String doSomething() {
        return "doSomething";
    }

//    public String blockHandler(BlockException exception) {
//        return "Exception: " + exception.getMessage();
//    }
//
//    public String fallback() {
//        return "Fallback";
//    }


    /**
     * 测试热点规则
     *
     * @param name
     * @return
     */
    @SentinelResource(value = "hello", blockHandler = "blockHandler1", fallback = "fallback1")
    public String hello(String name) {
        return "hello " + name;
    }

    public String blockHandler1(String name, BlockException exception) {
        return "Exception1: " + exception.getMessage();
    }

    public String fallback1(String name) {
        return "Fallback1 " + name;
    }

    /**
     * 用来测试异常的情况下熔断配置,可在sentinel控制台配置熔断策略
     *
     * @return
     */
    @SentinelResource(value = "doSomethingError", blockHandler = "blockHandler2", fallback = "fallback2")
    public String doSomethingError() {
        int a = 1 / 0;
        return "doSomethingError ";
    }

    public String blockHandler2(BlockException exception) {
        return "Exception2: " + exception.getRuleLimitApp().trim();
    }

    public String fallback2() {
        return "Fallback2";
    }

    /**
     * 用来测试超时的情况下熔断配置,可在sentinel控制台配置熔断策略
     * blockHandler和fallback都配置的情况下只有blockHandler生效
     *
     * @return
     */
    @SentinelResource(value = "doSomethingTimeout", blockHandler = "blockHandler3", fallback = "fallback3")
    public String doSomethingTimeout() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
        return "doSomethingTimeout ";
    }

    public String blockHandler3(BlockException exception) throws InterruptedException {
        return "Exception3: " + exception.getRuleLimitApp().trim();
    }

    public String fallback3() throws InterruptedException {
        return "Fallback3";
    }

}

接口:

@RestController
public class TestController {
    @Autowired
    private TestService service;

    @GetMapping()
    public String index() {
        return service.doSomething();
    }

    @GetMapping("hello")
    public String hello(String name) {
        return service.hello(name);
    }

    @GetMapping("e")
    public String error() {
        return service.doSomethingError();
    }

    @GetMapping("t")
    public String timeout() throws InterruptedException {
        return service.doSomethingTimeout();
    }
}
3.2.5 测试

调用测试接口,然后可以在sentinel控制台的簇点链路中看到对应的请求情况,之后就可以添加规则进行测试。

3.3 Feign 支持

3.3.1 添加依赖
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
3.3.2 添加配置
# 配置nacos
spring:
  application:
    name: rabb-sentinel
  cloud:
    nacos:
      server-addr: localhost:8848
      username: nacos
      password: nacos
# 开启 Sentinel 对 Feign 的支持
feign:
  sentinel:
    enabled: true
# Feign 远程调用的服务名称
rabb:
  service:
    provider-service: rabb-nacos-provider
3.3.3 开启FeignClients

启动类添加@EnableFeignClients注解

@SpringBootApplication
@EnableFeignClients
public class SentinelApplication {

    public static void main(String[] args) {
        SpringApplication.run(SentinelApplication.class, args);
    }

}
3.3.4 定义Feign接口

接口:

@FeignClient(value = "${rabb.service.provider-service}", fallbackFactory = OpenFeignFallbackFactoryService.class)
public interface OpenFeignService {

    @PostMapping("/openfeign/user")
    Response create(@RequestBody User user);

    @GetMapping("/openfeign/user/{id}")
    Response get(@PathVariable(value = "id") Integer id);

    @PutMapping("/openfeign/user/{id}")
    Response update(@PathVariable(value = "id") Integer id);

    @DeleteMapping("/openfeign/user/{id}")
    Response delete(@PathVariable(value = "id") Integer id);
}

回调工厂,获取异常:

@Component
@Slf4j
public class OpenFeignFallbackFactoryService implements FallbackFactory<OpenFeignService> {
    @Override
    public OpenFeignService create(Throwable throwable) {
        log.error("服务降级:异常信息:{}", throwable.getMessage(), throwable);
        return new OpenFeignFallbackService();
    }
}

回调类,定义熔断方法:

@FeignClient(value = "${rabb.service.provider-service}", fallbackFactory = OpenFeignFallbackFactoryService.class)
public interface OpenFeignService {

    @PostMapping("/openfeign/user")
    Response create(@RequestBody User user);

    @GetMapping("/openfeign/user/{id}")
    Response get(@PathVariable(value = "id") Integer id);

    @PutMapping("/openfeign/user/{id}")
    Response update(@PathVariable(value = "id") Integer id);

    @DeleteMapping("/openfeign/user/{id}")
    Response delete(@PathVariable(value = "id") Integer id);
}
3.3.5 添加对应接口
@RestController
@RequestMapping("/feign")
public class OpenfeignController {

    @Autowired
    OpenFeignService openFeignService;

    @GetMapping("/create")
    public Response create() {
        User user = new User(1, "lazyrabbit-" + LocalDateTime.now().toString());
        return openFeignService.create(user);
    }

    @GetMapping("/get")
    public Response get() {
        return openFeignService.get(2);
    }

    @GetMapping("/update")
    public Response update() {
        return openFeignService.update(3);
    }

    @GetMapping("/delete")
    public Response delete() {
        return openFeignService.delete(4);
    }

}
3.3.6 测试

启动nacos,feign服务端程序(参考Nacos篇)以及当前程序,调用OpenfeignController中的接口,可以在sentinel中看到针对接口以及openFeignService的方法都生成了对应的资源,然后添加规则进行测试即可。

3.4 RestTemplate 支持

Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,在构造 RestTemplate bean的时候需要加上 @SentinelRestTemplate 注解。

@Bean
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
    return new RestTemplate();
}

@SentinelRestTemplate 注解的属性支持限流(blockHandler, blockHandlerClass)和降级(fallback, fallbackClass)的处理。

其中 blockHandler 或 fallback 属性对应的方法必须是对应 blockHandlerClass 或 fallbackClass 属性中的静态方法。

该方法的参数跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor 方法一致,其中参数多出了一个 BlockException 参数用于获取 Sentinel 捕获的异常。

比如上述 @SentinelRestTemplate 注解中 ExceptionUtil 的 handleException 属性对应的方法声明如下:

public class ExceptionUtil {
    public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) {
        ...
    }
}

@SentinelRestTemplate 注解的限流(blockHandler, blockHandlerClass)和降级(fallback, fallbackClass)属性不强制填写。

当使用 RestTemplate 调用被 Sentinel 熔断后,会返回 RestTemplate request block by sentinel 信息,或者也可以编写对应的方法自行处理返回信息。这里提供了 SentinelClientHttpResponse 用于构造返回信息。

Sentinel RestTemplate 限流的资源规则提供两种粒度:

  • httpmethod:schema://host:port/path:协议、主机、端口和路径
  • httpmethod:schema://host:port:协议、主机和端口

3.5 统一异常处理

在 SentinelWebInterceptor 拦截器中,当请求满足配置的 Sentinel block 的条件时,Sentinel 会抛出 BlockException 异常。通过定义 BlockExceptionHandler 接口的实现类,可以实现对 BlockException 的异常处理。

@Slf4j
@Component
public class MySentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        log.info("rule:{}", e.getRule());
        // 自定义返回结果
        Response responseObj = null;
        if (e instanceof FlowException) {
            // 限流
            responseObj = ResponseUtils.failure("接口限流");
        } else if (e instanceof DegradeException) {
            // 降级
            responseObj = ResponseUtils.failure("服务降级");
        } else if (e instanceof ParamFlowException) {
            // 热点参数
            responseObj = ResponseUtils.failure("热点参数限流");
        } else if (e instanceof SystemBlockException) {
            // 系统保护
            responseObj = ResponseUtils.failure("触发系统保护规则");
        } else if (e instanceof AuthorityException) {
            // 授权规则
            responseObj = ResponseUtils.failure("授权规则不通过");
        }
        //返回json数据
        httpServletResponse.setStatus(200);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        //springmvc 的一个json转换类 (jackson)
        new ObjectMapper().writeValue(httpServletResponse.getWriter(), responseObj);
        //重定向
        //response.sendRedirect("http://www.baidu.com");
    }
}

3.6 使用nacos配置中心持久化规则

3.6.1 添加依赖
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
3.6.2 配置

添加sentinel.datasource配置:

spring:
  application:
    name: rabb-sentinel
  cloud:
    nacos:
      server-addr: localhost:8848
      username: nacos
      password: nacos
    sentinel:
      transport:
        dashboard: localhost:8880
        port: 8719
      datasource:
        ds:
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: rabb-sentinel-rule
            group-id: DEFAULT_GROUP
            username: nacos
            password: nacos
            data-type: json
            rule-type: flow
      eager: true

ds 是 ReadableDataSource 的名字,可随意编写。后面的 file ,zk ,nacos , apollo 就是对应具体的数据源,它们后面的配置就是这些具体数据源各自的配置。注意数据源的依赖要单独引入(比如 sentinel-datasource-nacos)。
每种数据源都有两个共同的配置项: data-type、 converter-class 以及 rule-type。

  • data-type 配置项表示 Converter 类型,Spring Cloud Alibaba Sentinel 默认提供两种内置的值,分别是 json 和 xml (不填默认是json)。
  • 如果不想使用内置的 json 或 xml 这两种 Converter,可以填写 custom 表示自定义 Converter,然后再配置 converter-class 配置项,该配置项需要写类的全路径名(比如 spring.cloud.sentinel.datasource.ds1.file.converter-class=com.alibaba.cloud.examples.JsonFlowRuleListConverter)。
  • rule-type 配置表示该数据源中的规则属于哪种类型的规则(flow, degrade, authority, system, param-flow, gw-flow, gw-api-group)。注意网关流控规则 (GatewayFlowRule) 对应 gw-flow。
3.6.3 在nacos添加流控规则

在nacos中添加一个名称rabb-sentinel-rule的配置,其值如下:

[
    {
        "resource": "doSomething",
        "limitApp": "default",
        "grade": 1,
        "count": 4,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

对应属性:

  • resource:资源名,即限流规则的作用对象
  • limitApp:流控针对的调用来源,若为 default 则不区分调用来源
  • grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
  • count:限流阈值
  • strategy:调用关系限流策略
  • controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
  • clusterMode:是否为集群模式
3.6.4 测试

重新启动各个工程,登录sentinel控制台,可以在流控规则页面中发现3.6.3中的配置规则。

修改nacos中配置,sentinel能及时同步过来。sentinel中的配置暂时无法同步到nacos中。

参考

https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel