Sentinel:Spring Cloud Alibaba高可用流量控制组件

发布于:2023-01-10 ⋅ 阅读:(190) ⋅ 点赞:(0)

Sentinel:Spring Cloud Alibaba高可用流量控制组件

Sentinel 是由阿里巴巴中间件团队开发的开源项目,是一种面向分布式微服务架构的轻量级高可用流量控制组件。

中文网站:介绍 · alibaba/Sentinel Wiki · GitHubintroduction (sentinelguard.io)

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
  • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它可以在 ClassPath 路径下的 META-INF/services 文件夹查找文件,并自动加载文件中定义的类。

从功能上来说,Sentinel 与 Spring Cloud Netfilx Hystrix 类似,但 Sentinel 要比 Hystrix 更加强大,例如 Sentinel 提供了流量控制功能、比 Hystrix 更加完善的实时监控功能等等。

Sentinel 的主要特性:

在这里插入图片描述

Sentinel 的组成

Sentinel 主要由以下两个部分组成:

  • Sentinel 核心库:Sentinel 的核心库不依赖任何框架或库,能够运行于 Java 8 及以上的版本的运行时环境中,同时对 Spring Cloud、Dubbo 等微服务框架提供了很好的支持。

  • Sentinel 控制台(Dashboard):Sentinel 提供的一个轻量级的开源控制台,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。

    Sentinel 核心库不依赖 Sentinel Dashboard,但两者结合使用可以有效的提高效率,让 Sentinel 发挥它最大的作用。

Sentinel 的基本概念

资源

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

规则

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

Sentinel 功能和设计理念

流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:

在这里插入图片描述

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

熔断降级

什么是熔断降级

除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。这个问题和 Hystrix 里面描述的问题是一样的。

在这里插入图片描述

Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。

熔断降级设计理念

在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。

Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。

Sentinel 对这个问题采取了两种手段:

  • 通过并发线程数进行限制

和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。

  • 通过响应时间对资源进行降级

除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

系统负载保护

Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

Sentinel 是如何工作的

Sentinel 的主要工作机制如下:

  • 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
  • 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
  • Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。

@SentinelResource 注解

@SentinelResource 注解是 Sentinel 提供的最重要的注解之一,它还包含了多个属性,如下表。

属性 说明 必填与否 使用要求
value 用于指定资源的名称 必填 -
entryType entry 类型 可选项(默认为 EntryType.OUT) -
blockHandler 服务限流后会抛出 BlockException 异常,而 blockHandler 则是用来指定一个函数来处理 BlockException 异常的。 简单点说,该属性用于指定服务限流后的后续处理逻辑。 可选项 blockHandler 函数访问范围需要是 public;返回类型需要与原方法相匹配;参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException;blockHandler 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 blockHandler 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
blockHandlerClass 若 blockHandler 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 可选项 不能单独使用,必须与 blockHandler 属性配合使用;该属性指定的类中的 blockHandler 函数必须为 static 函数,否则无法解析。
fallback 用于在抛出异常(包括 BlockException)时,提供 fallback 处理逻辑。 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。 可选项 返回值类型必须与原函数返回值类型一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
fallbackClass 若 fallback 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 可选项 不能单独使用,必须与 fallback 或 defaultFallback 属性配合使用;该属性指定的类中的 fallback 函数必须为 static 函数,否则无法解析。
defaultFallback 默认的 fallback 函数名称,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。 默认 fallback 函数可以针对所以类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。 可选项 返回值类型必须与原函数返回值类型一致;方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
exceptionsToIgnore 用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。 可选项 -

注:在 Sentinel 1.6.0 之前,fallback 函数只针对降级异常(DegradeException)进行处理,不能处理业务异常。

Sentinel 控制台

Sentinel 提供了一个轻量级的开源控制台 Sentinel Dashboard,它提供了机器发现与健康情况管理、监控(单机和集群)、规则管理与推送等多种功能。

Sentinel 控制台提供的功能如下:

  • 查看机器列表以及健康情况:Sentinel 控制台能够收集 Sentinel 客户端发送的心跳包,判断机器是否在线。
  • 监控(单机和集群聚合):Sentinel 控制台通过 Sentinel 客户端暴露的监控 API,可以实现秒级的实时监控。
  • 规则管理和推送:通过 Sentinel 控制台,我们还能够针对资源定义和推送规则。
  • 鉴权:从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。

Sentinel Dashboard 是我们配置和管理规则(例如流控规则、熔断降级规则等)的重要入口之一。通过它,我们不仅可以对规则进行配置和管理,还能实时查看规则的效果。

安装 Sentinel 控制台

下载和安装 Sentinel 控制台,具体步骤如下。

下载地址:Releases · alibaba/Sentinel · GitHub,根据需求下载合适的版本
在这里插入图片描述

  1. 打开命令行窗口,跳转到 Sentinel Dashboard jar 包所在的目录,执行以下命令,启动 Sentinel Dashboard。
java -jar sentinel-dashboard-1.8.3.jar
  1. 启动完成后,使用浏览器访问“http://localhost:8080/”,跳转到 Sentinel 控制台登陆页面,如下图。

在这里插入图片描述

  1. 分别输入用户名和密码(默认都是 sentinel),点击下方的登录按钮,结果如下图。
    在这里插入图片描述

Sentinel 的开发流程

Sentinel 的开发流程如下:

  1. 引入 Sentinel 依赖:在项目中引入 Sentinel 的依赖,将 Sentinel 整合到项目中;
  2. 定义资源:通过对主流框架提供适配或 Sentinel 提供的显式 API 和注解,可以定义需要保护的资源,此外 Sentinel 还提供了资源的实时统计和调用链路分析;
  3. 定义规则:根据实时统计信息,对资源定义规则,例如流控规则、熔断规则、热点规则、系统规则以及授权规则等。
  4. 检验规则是否在生效:运行程序,检验规则是否生效,查看效果。

引入 Sentinel 依赖

为了减少开发的复杂程度,Sentinel 对大部分的主流框架都进行了适配,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux 和 Reactor 等。以 Spring Cloud 为例,我们只需要引入 spring-cloud-starter-alibaba-sentinel 的依赖,就可以方便地将 Sentinel 整合到项目中。

  1. 创建一个名为 sentinel-service-8401 的 Spring Boot 模块,并在其 pom.xml 中添加以下依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>alibaba-parent</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sentinel-service-8401</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>
        <!--Nacos 服务发现依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--Snetinel 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
  1. 添加配置文件application.yml

    server:
      port: 8401 #端口
    
    spring:
      application:
        name: sentinel-service #服务名
      cloud:
        nacos:
          discovery:
            #Nacos服务注册中心(集群)地址
            server-addr: localhost:8848
        sentinel:
          transport:
            #配置 Sentinel dashboard 地址
            dashboard: localhost:8080
            #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
            port: 8719
    
    management:
      endpoints:
        web:
          exposure:
            include: '*'
    
  2. 创建一个名为 SentinelController 的 Controller 类,进行测试,代码如下。

@RestController
@Slf4j
public class SentinelController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/testA")
    public String testA() {
        return "服务访问成功------testA";
    }
    @GetMapping("/testB")
    public String testB() {
        return "服务访问成功------testB";
    }

}
  1. sentinel-service-8401 主启动类如下。
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelServiceApplication {

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

}
  1. 依次启动 Nacos Server 、 sentinel-service-8401,使用浏览器访问“http://localhost:8401/testA”,结果如下图。

在这里插入图片描述

  1. 使用浏览器访问 Sentinel 控制台主页,我们发现在“首页”下方新增了一个“sentinel-servcie”的菜单,而这正是 spring-cloud-alibaba-sentinel-service-8401 的服务名(spring.application.name),说明 Sentinel 已经监控到这个服务,如下图。

在这里插入图片描述

  1. 刷新请求,点击实时监控,查看监控页面

在这里插入图片描述

  1. 查看簇点链路
    在这里插入图片描述

  2. 例如通过sentinel实现限流,修改/testA资源的流控规则,如下

在这里插入图片描述
在这里插入图片描述

修改单机阈值为1,重新刷新/testA请求,当QPS超过1时,会提示:

在这里插入图片描述

此时实时监控处:
在这里插入图片描述

定义资源

资源是 Sentinel 中的核心概念之一。在项目开发时,我们只需要考虑这个服务、方法或代码是否需要保护,如果需要保护,就可以将它定义为一个资源。

Sentinel 为我们提供了多种定义资源的方式:

  • 适配主流框架自动定义资源
  • 通过 SphU 手动定义资源
  • 通过 SphO 手动定义资源
  • 注解方式定义资源

适配主流框架自动定义资源

Sentinel 对大部分的主流框架都进行了适配,我们只要引入相关的适配模块(例如 spring-cloud-starter-alibaba-sentinel),Snetinel 就会自动将项目中的服务(包括调用端和服务端)定义为资源,资源名就是服务的请求路径。此时,我们只要再定义一些规则,这些资源就可以享受到 Sentinel 的保护。

我们可以在 Sentinel 控制台的“簇点链路”中,直接查看被 Sentinel 监控的资源,如下图。

在这里插入图片描述

通过 SphU 手动定义资源

Sentinel 提供了一个名为 SphU 的类,它包含的 try-catch 风格的 API ,可以帮助我们手动定义资源。

下面我们就通过一个实例,来演示下如何通过 SphU 定义资源。

  1. 在 sentinel-service-8401 下的 SentinelController 中,新增一个 testAbySphU() 方法定义一个名为 testAbySphU 的资源,代码如下。
	/**
     * 通过 SphU 手动定义资源
     * @return
     */
    public String testAbySphU() {
        Entry entry = null;
        try {
            entry = SphU.entry("testAbySphU");
            //您的业务逻辑 - 开始
            log.info("服务访问成功------testA:"+serverPort);
            return "服务访问成功------testA:"+serverPort;
            //您的业务逻辑 - 结束
        } catch (BlockException e1) {
            //流控逻辑处理 - 开始
            log.info("testA 服务被限流");
            return "testA 服务被限流";
            //流控逻辑处理 - 结束
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }

通过 SphO 手动定义资源

Sentinel 还提供了一个名为 SphO 的类,它包含了 if-else 风格的 API,能帮助我们手动定义资源。通过这种方式定义的资源,发生了限流之后会返回 false,此时我们可以根据返回值,进行限流之后的逻辑处理。

	    
@RestController
@Slf4j
public class SentinelController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/testA")
    public String testA() {
        return testAbySphU();
    }
    @GetMapping("/testB")
    public String testB() {
        return testBbySphO();
    }


    /**
     * 通过 SphU 手动定义资源
     * @return
     */
    public String testAbySphU() {
        Entry entry = null;
        try {
            entry = SphU.entry("testAbySphU");
            //您的业务逻辑 - 开始
            log.info("服务访问成功------testA:"+serverPort);
            return "服务访问成功------testA:"+serverPort;
            //您的业务逻辑 - 结束
        } catch (BlockException e1) {
            //流控逻辑处理 - 开始
            log.info("testA 服务被限流");
            return "testA 服务被限流";
            //流控逻辑处理 - 结束
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }
    /**
     * 通过 SphO 手动定义资源
     *
     * @return
     */
    public String testBbySphO() {
        if (SphO.entry("testBbySphO")) {
            // 务必保证finally会被执行
            try {
                log.info("服务访问成功------testB:" + serverPort);
                return "服务访问成功------testB:" + serverPort;
            } finally {
                SphO.exit();
            }
        } else {
            // 资源访问阻止,被限流或被降级
            //流控逻辑处理 - 开始
            log.info("testB 服务被限流");
            return "testB 服务被限流";
            //流控逻辑处理 - 结束
        }
    }
}

注解方式定义资源(推荐)

除了以上两种方式外,我们还可以通过 Sentinel 提供的 @SentinelResource 注解定义资源。

sentinel-service-8401 中 SentinelController 类中增加以下代码。

/**
 * 通过SentinelResource注解定义资源
 * @return
 */
@GetMapping("/testC")
@SentinelResource(value = "testCbyAnnotation")
public String testC() {
    return "服务访问成功------testC..........";
}

启动服务,访问localhost:8401/testAlocalhost:8401/testBlocalhost:8401/testC,查看sentinel控制台,可以看到定义的资源。

在这里插入图片描述

规则的种类

Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略。

Sentinel 支持以下几种规则:流量控制规则熔断降级规则系统保护规则来源访问控制规则热点参数规则

流量控制规则 (FlowRule)

任何系统处理请求的能力都是有限的,但任意时间内到达系统的请求量往往是随机且不可控的,如果在某一个瞬时时刻请求量急剧增,那么系统就很有可能被瞬时的流量高峰冲垮。为了避免此类情况发生,我们都需要根据系统的处理能力对请求流量进行控制,这就是我们常说的“流量控制”,简称“流控”。

Sentinel 作为一种轻量级高可用流量控制组件,流量控制是它最主要的工作之一。

我们可以针对资源定义流控规则,Sentinel 会根据这些规则对流量相关的各项指标进行监控。当这些指标当达到或超过流控规则规定的阈值时,Sentinel 会对请求的流量进行限制(即“限流”),以避免系统被瞬时的流量高峰冲垮,保障系统的高可用性。

一条流量规则主要由下表中的属性组成,我们可以通过组合这些属性来实现不同的限流效果。

流量规则的定义

重要属性:

Field 说明 默认值
resource 资源名,资源名是限流规则的作用对象
count 限流阈值
grade 限流阈值类型,QPS 或线程数模式 QPS 模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 直接拒绝

QPS 表示并发请求数,换句话说就是,每秒钟最多通过的请求数。

同一个资源可以创建多条流控规则,Sentinel 会遍历这些规则,直到有规则触发限流或者所有规则遍历完毕为止。

Sentinel 触发限流时,资源会抛出 BlockException 异常,此时我们可以捕捉 BlockException 来自定义被限流之后的处理逻辑。

通过 Sentinel 控制台定义流控规则

通过 Sentinel 控制台,直接对资源定义流控规则,操作步骤如下

例如通过sentinel实现限流,修改/testA资源的流控规则,如下

  1. 新增流控规则

在这里插入图片描述

在这里插入图片描述

  1. 修改单机阈值为1,重新刷新/testA请求,当QPS超过1时,会提示:

在这里插入图片描述

此时实时监控处:在这里插入图片描述

若页面中出现以上信息,则说明该服务已被限流,但这种提示是 Sentinel 系统自动生成的,用户体验不好。

  1. 在服务代码中使用 @SentinelResource 注解定义资源名称,并在 blockHandler 属性指定一个限流函数,自定义服务限流信息,代码如下。
/**
 * 通过SentinelResource注解定义资源,当前请求触发限流后执行的函数
 * @return
 */
@GetMapping("/testC")
@SentinelResource(value = "testCbyAnnotation",blockHandler ="blockHandlerTestC" )
public String testC() {
    return "服务访问成功------testC..........";
}

public String blockHandlerTestC(BlockException exception) {
    return "testC服务访问失败! 您已被限流,请稍后重试";
}

通过 @SentinelResource 注解的 blockHandler 属性指定了一个 blockHandler 函数,进行限流之后的后续处理。

使用 @SentinelResource 注解的 blockHandler 属性时,需要注意以下事项:

  • blockHandler 函数访问范围需要是 public;
  • 返回类型需要与原方法相匹配;
  • 参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException;
  • blockHandler 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 blockHandlerClass为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • 请务必添加 blockHandler 属性来指定自定义的限流处理方法,若不指定,则会跳转到错误页

在这里插入图片描述

  1. 重启服务,重新添加流控规则,访问结果如下:
    在这里插入图片描述

通过代码定义流控规则

还可以在服务代码中,调用 FlowRuleManager 类的 loadRules() 方法来定义流控规则,该方法需要一个 FlowRule 类型的 List 集合作为其参数,代码如下。

public static void loadRules(List<FlowRule> rules) {
    currentProperty.updateValue(rules); 
}

FlowRule 可以通过以下属性定义流控规则,如下表。

属性 说明 默认值
resource 资源名,即流控规则的作用对象 -
count 限流的阈值。 -
grade 流控阈值的类型,包括 QPS 或并发线程数 QPS
limitApp 流控针对的调用来源 default,表示不区分调用来源
strategy 调用关系限流策略,包括直接、链路和关联 直接
controlBehavior 流控效果(直接拒绝、Warm Up、匀速排队),不支持按调用关系限流 直接拒绝

通过代码定义流控规则,步骤如下。

  1. 在添加一个 initFlowRules() 方法,为名为 testCbyAnnotation的资源定义流控规则:每秒最多只能通过 2 个请求,即 QPS 的阈值为 2
	/**
     * 代码设置流控规则
     */
    private static void initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<>();
        //定义一个限流规则对象
        FlowRule rule1 = new FlowRule();
        //资源名称
        rule1.setResource("testCbyAnnotation");
        // Set max qps to 2
        rule1.setCount(2);
        //限流阈值的类型
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        //定义限流规则
        FlowRuleManager.loadRules(rules);
    }
  1. 重启 sentinel-service-8401,并使用浏览器访问“http://localhost:8401/testC”,当访问频率超过2时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EOViBB24-1660888778326)(file://F:\Java课件\SpringCloud\images\image-20220513133311904.png?lastModify=1652420812)]

流控模式

流控—线程数直接失败

在这里插入图片描述

Sentinel流控—关联

  • 当自己关联的资源达到阈值时,就限流自己
  • 当与A关联的资源B达到阀值后,就限流A自己(B惹事,A挂了)

设置testD请求

当关联资源/testC的QPS阀值超过1时,就限流/testD的Rest访问地址,当关联资源到阈值后限制配置好的资源名

定义testCResource资源的阈值是1

在这里插入图片描述

当testCResource达到阈值时,testDResource触发限流:

在这里插入图片描述

利用postman模拟并发访问测试:

在这里插入图片描述

添加并发访问:

在这里插入图片描述

在这里插入图片描述

点击RUN Test模拟testCResource资源并发访问,此时点击浏览器访问testD,自动限流
在这里插入图片描述

流控模式-链路(根据调用链路入口限流:链路限流)

微服务中,资源之间的调用链路,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。

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

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

上图中来自入口 Entrance1Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。

简而言之:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。

例如有两条请求链路:

  • /test1 --> /common
  • /test2 --> /common

如果只希望统计从/test2进入到/common的请求,则可以这样配置
在这里插入图片描述

流控效果

流控效果是指请求达到流控阈值时应该采取的措施,包括三种:

  • 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
  • warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
  • 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长

流控效果-warm up

​ warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 threshold / coldFactor,持续指定时长后,逐渐提高到threshold值。而coldFactor的默认值是3. 例如,我设置QPS的threshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10

在这里插入图片描述

流控效果-排队等待

​ 当请求超过QPS阈值时,快速失败和warm up 会拒绝新的请求并抛出异常。而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。

​ 例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待超过2000ms的请求会被拒绝并抛出异常

在这里插入图片描述

热点参数限流

热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。

在这里插入图片描述

例如: 代表的含义是:对testEResource这个资源的0号参数(第一个参数)做统计,每1秒相同参数值的请求数不能超过2

在这里插入图片描述

@GetMapping("/testE/{id}")
@SentinelResource(value = "testEResource",
        blockHandlerClass = {MyBlockHandler.class},
        blockHandler = "blockHandlerTestE"
)
public String testE(@PathVariable("id") Integer id) {
    return "服务访问成功------testE..........." + id;
}

在热点参数限流的高级选项中,可以对部分参数设置例外配置:

在这里插入图片描述

结合上一个配置,这里的含义是对0号的int类型参数限流,每1秒相同参数的QPS不能超过2,有两个例外:

  • 如果参数值是100,则每1秒允许的QPS为5
  • 如果参数值是101,则每1秒允许的QPS为3

熔断降级规则 (DegradeRule)

除了流量控制以外,对调用链路中不稳定资源的熔断降级,也是保障服务高可用的重要措施之一。

在分布式微服务架构中,一个系统往往由多个服务组成,不同服务之间相互调用,组成复杂的调用链路。如果链路上的某一个服务出现故障,那么故障就会沿着调用链路在系统中蔓延,最终导致整个系统瘫痪。Sentinel 提供了熔断降级机制就可以解决这个问题。

Sentinel 的熔断将机制会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),暂时切断对这个资源的调用,以避免局部不稳定因素导致整个系统的雪崩。

熔断降级作为服务保护自身的手段,通常在客户端(调用端)进行配置,资源被熔断降级最直接的表现就是抛出 DegradeException 异常。

Sentinel 熔断策略

Sentinel 提供了 3 种熔断策略,如下表所示。

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

注意:Sentinel 1.8.0 版本对熔断降级特性进行了全新的改进升级,以上熔断策略针对的是 Sentinel 1.8.0 及以上版本。

Sentinel 熔断状态

Sentinel 熔断降级中共涉及 3 种状态,熔断状态的之间的转换过程如下图。

在这里插入图片描述

Sentinel 熔断降级中共涉及 3 种状态,如下表。

状态 说明 触发条件
熔断关闭状态 (CLOSED) 处于关闭状态时,请求可以正常调用资源。 满足以下任意条件,Sentinel 熔断器进入熔断关闭状态:全部请求访问成功。单位统计时长(statIntervalMs)内请求数目小于设置的最小请求数目。未达到熔断标准,例如服务超时比例、异常数、异常比例未达到阈值。处于探测恢复状态时,下一个请求访问成功。
熔断开启状态 (OPEN) 处于熔断开启状态时,熔断器会一定的时间(规定的熔断时长)内,暂时切断所有请求对该资源的调用,并调用相应的降级逻辑使请求快速失败避免系统崩溃。 满足以下任意条件,Sentinel 熔断器进入熔断开启状态:单位统计时长内请求数目大于设置的最小请求数目,且已达到熔断标准,例如请求超时比例、异常数、异常比例达到阈值。处于探测恢复状态时,下一个请求访问失败。
探测恢复状态 (HALF-OPEN) 处于探测恢复状态时,Sentinel 熔断器会允许一个请求调用资源。则若接下来的一个请求成功完成(没有错误)则结束熔断,熔断器进入熔断关闭(CLOSED)状态;否则会再次被熔断,熔断器进入熔断开启(OPEN)状态。 在熔断开启一段时间(降级窗口时间或熔断时长,单位为 s)后,Sentinel 熔断器自动会进入探测恢复状态。

Sentinel 熔断规则属性

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

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 引入)

同一个资源可以同时有多个降级规则。

Sentinel 实现熔断降级过程

Sentinel 实现熔断降级的步骤如下:

  1. 在项目中,使用 @SentinelResource 注解的 fallback 属性可以为资源指定熔断降级逻辑(方法)。
  2. 通过 Sentinel 控制台或代码定义熔断规则,包括熔断策略、最小请求数、阈值、熔断时长以及统计时长等。
  3. 若单位统计时长(statIntervalMs)内,请求数目大于设置的最小请求数目且达到熔断标准(例如请求超时比例、异常数、异常比例达到阈值),Sentinel 熔断器进入熔断开启状态(OPEN)。
  4. 处于熔断开启状态时, @SentinelResource 注解的 fallback 属性指定的降级逻辑会临时充当主业务逻辑,而原来的主逻辑则暂时不可用。当有请求访问该资源时,会直接调用降级逻辑使请求快速失败,而不会调用原来的主业务逻辑。
  5. 在经过一段时间(在熔断规则中设置的熔断时长)后,熔断器会进入探测恢复状态(HALF-OPEN),此时 Sentinel 会允许一个请求对原来的主业务逻辑进行调用,并监控其调用结果。
  6. 若请求调用成功,则熔断器进入熔断关闭状态(CLOSED ),服务原来的主业务逻辑恢复,否则重新进入熔断开启状态(OPEN)。

通过 Sentinel 控制台定义熔断降级规则

通过 Sentinel 控制台直接对资源定义熔断降级规则

下面通过一个实例,来演示如何通过 Sentinel 控制台,对资源定义降级规则。

  1. 修改user-service的get/{id}请求,利用线程添加延迟操作,list请求正常执行模拟。
@RestController
@Slf4j
public class UserController {

    @GetMapping("/get/{id}")
    public String getInfo(@PathVariable("id") Integer id){
        //模拟根据id获取时延迟1s
        try {
            TimeUnit.SECONDS.sleep(1);
            log.info("休眠 1秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "用户服务正常执行,获取id为" + id + "的用户信息成功";
    }

    @GetMapping("/list")
    public String getList(){
        return "获取全部用户信息成功!!!!!!";
    }

}
  1. 修改goods-service,基于openfeign调用用户服务get与list接口
@RestController
public class GoodsController {

    @Autowired
    private UserFeign userFeign;

    @GetMapping("/consumer/get/{id}")
    @SentinelResource(value = "fallback", fallback = "handlerFallback")
    public String get(@PathVariable("id") Integer id) {
        //调用监听事件,用于测试熔断器状态
        monitor();
        //调用用户接口
        String info = userFeign.getInfo(id);
        if (id == 6) {
            System.err.println("--------->>>>主业务逻辑,抛出非法参数异常,id = " + id);
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
            //如果查到的记录也是 null 也控制正异常
        }
        return info;
    }

    @GetMapping("/consumer/list")
    public String list() {
        return userFeign.getList();
    }


    //处理异常的回退方法(服务降级)
    public String handlerFallback(@PathVariable Integer id, Throwable e) {
        System.err.println("--------->>>>服务降级逻辑");
        return "id为" + id + "--------服务被降级!异常信息为:" + e.getMessage();
    }

    /**
     * 自定义事件监听器,监听熔断器状态转换
     */
    public void monitor() {
        EventObserverRegistry.getInstance().addStateChangeObserver("logging",
                (prevState, newState, rule, snapshotValue) -> {
                    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    if (newState == CircuitBreaker.State.OPEN) {
                        // 变换至 OPEN state 时会携带触发时的值
                        System.err.println(String.format("%s -> OPEN at %s, 发送请求次数=%.2f", prevState.name(),
                                format.format(new Date(TimeUtil.currentTimeMillis())), snapshotValue));
                    } else {
                        System.err.println(String.format("%s -> %s at %s", prevState.name(), newState.name(),
                                format.format(new Date(TimeUtil.currentTimeMillis()))));
                    }
                });
    }


}

通过 @SentinelResource 注解的 fallback 属性指定了一个 fallback 函数,进行熔断降级的后续处理。

使用 @SentinelResource 注解的 blockHandler 属性时,需要注意以下事项:

  • 返回值类型必须与原函数返回值类型一致;

  • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;

  • fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  1. 在goods-service中的配置文件中添加feign整合sentinel
server:
  port: 8071
spring:
  application:
    name: goods-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置 Sentinel dashboard 地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
feign:
  sentinel:
    enabled: true   #开启feign启用sentinel
  1. 启动user-service和goods-service,打开sentinel控制台,设置资源的熔断规则
    在这里插入图片描述

熔断策略可以时慢调用比例、异常比例、异常数。以上图为例,统计时间2s内,最少请求2次,异常比例达到0.5,进行熔断,熔断时长5s。

请求异常请求:localhost:8071/consumer/get/6,频率达到2s至少两次,此时进入熔断,接着访问localhost:8071/consumer/get/4,此时正常请求默认快速失败
在这里插入图片描述

查看控制台打印,可以看到熔断器状态切换

在这里插入图片描述

系统保护规则 (SystemRule)

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

​ 系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

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

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

系统规则包含下面几个重要的属性:

Field 说明 默认值
highestSystemLoad load1 触发值,用于触发自适应控制阶段 -1 (不生效)
avgRt 所有入口流量的平均响应时间 -1 (不生效)
maxThread 入口流量的最大并发数 -1 (不生效)
qps 所有入口资源的 QPS -1 (不生效)
highestCpuUsage 当前系统的 CPU 使用率(0.0-1.0) -1 (不生效)

在这里插入图片描述

访问控制规则 (AuthorityRule)

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

授权规则,即黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • resource:资源名,即限流规则的作用对象
  • limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
  • strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式
    在这里插入图片描述

流控应用填写的是调用方标识,Sentinel提供了RequestOriginParser接口来处理标识信息。

只要Sentinel保护的接口资源被访问,Sentinel就会调用RequestOriginParser的实现类去解析调用者标识。

  1. 自定义调用方标识处理规则
@Component
public class RequestOriginParserDefinition implements RequestOriginParser {

    /**
     * 根据请求参数serviceName进行标识处理
     * @param request
     * @return
     */
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String serviceName = request.getParameter("serviceName");
        StringBuffer url = request.getRequestURL();
        if (url.toString().endsWith("favicon.ico")) {
            // 浏览器会向后台请求favicon.ico图标
            return serviceName;
        }

        if (StringUtils.isEmpty(serviceName)) {
            throw new IllegalArgumentException("serviceName must not be null");
        }
        return serviceName;
    }
}
  1. 在GoodsController中添加测试接口

    @GetMapping("/goods/msg")
    public String goodsMsg(String serviceName){
        return serviceName;
    }
    
  2. 配置授权规则

这个配置的意思是,serviceName=pc不能访问(黑名单)

在这里插入图片描述

  1. 此时访问localhost:8071/goods/msg?serviceName=pc
    在这里插入图片描述

动态规则扩展

dynamic-rule-configuration (sentinelguard.io)

规则

Sentinel 的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则:

  • 通过 API 直接修改 (loadRules)
  • 通过 DataSource 适配不同数据源修改

DataSource 扩展

上述 loadRules() 方法只接受内存态的规则对象,但更多时候规则存储在文件、数据库或者配置中心当中。DataSource 接口给我们提供了对接任意配置源的能力。相比直接通过 API 修改规则,实现 DataSource 接口是更加可靠的做法。

我们推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现 ReadableDataSource 接口端监听规则中心实时获取变更,流程如下:

在这里插入图片描述

DataSource 扩展常见的实现方式有:

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。

Sentinel 目前支持以下数据源扩展:

推模式:使用 Nacos 配置规则

Nacos 是阿里中间件团队开源的服务发现和动态配置中心。Sentinel 针对 Nacos 作了适配,底层可以采用 Nacos 作为规则配置数据源。使用时只需添加以下依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>x.y.z</version>
</dependency>

然后创建 NacosDataSource 并将其注册至对应的 RuleManager 上即可。比如:

// remoteAddress 代表 Nacos 服务端的地址
// groupId 和 dataId 对应 Nacos 中相应配置
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
    source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());

官方案例地址:Sentinel/sentinel-demo/sentinel-demo-nacos-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/nacos at master · alibaba/Sentinel · GitHub