likes
comments
collection
share

SpringCloud实践系列(五):Sentinel流控

作者站长头像
站长
· 阅读数 17

SpringCloud实践系列(一):Nacos注册中心

Nacos: 注册中心,解决服务注册与发现

SpringCloud实践系列(二):Ribbon负载均衡

Ribbon: 客户端的负载均衡器,解决服务集群的负载均衡

SpringCloud实践系列(三):OpenFeign服务调用

OpenFeign:声明式的HTTP客户端,服务远程调用

SpringCloud实践系列(四):Nacos配置中心

Nacos:配置中心,中心化管理配置文件

SpringCloud实践系列(五):Sentinel流控

Sentinel:微服务流量卫兵,以流量为入口,保护微服务,防止出现服务雪崩

SpringCloud实践系列(六):Gateway网关(待更新)

Gateway: 微服务网关,服务集群的入口,路由转发以及负载均衡(结合Sentinel)

SpringCloud实践系列(七):Sleuth链路追踪(待更新)

Sleuth: 链路追踪,链路快速梳理、故障定位等

SpringCloud实践系列(八):Seata分布式事务(待更新)

Seata: 分布式事务解决方案

一、概述

1.1、高并发带来的问题

在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。

1.2、服务雪崩

1.2.1、服务雪崩现象

【现象】

一个服务不可用导致一些列的服务不可用

SpringCloud实践系列(五):Sentinel流控

1.3、常见容错方案

要防止雪崩的扩散,我们就要做好服务的容错,容错说白了就是保护自己不被猪队友拖垮的一些措施。

常见的容错思路有隔离、超时、限流、熔断、降级这几种。

1.3.1、隔离机制

比如服务A内总共有100个线程, 现在服务A可能会调用服务B,服务C,服务D.我们在服务A进行远程调用的时候,给不同的服务分配固定的线程,不会把所有线程都分配给某个微服务. 比如调用服务B分配30个线程,调用服务C分配30个线程,调用服务D分配40个线程. 这样进行资源的隔离,保证即使下游某个服务挂了,也不至于把服务A的线程消耗完。比如服务B挂了,这时候最多只会占用服务A的30个线程,服务A还有70个线程可以调用服务C和服务D。

SpringCloud实践系列(五):Sentinel流控

1.3.2、超时机制

在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断开请求,释放掉线程。

SpringCloud实践系列(五):Sentinel流控

1.3.3、限流机制

限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。

SpringCloud实践系列(五):Sentinel流控

1.3.4、熔断机制

在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。

服务熔断一般有三种状态:

  1. 熔断关闭状态(Closed):服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
  2. 熔断开启状态(Open):后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法。
  3. 半熔断状态(Half-Open):尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。

SpringCloud实践系列(五):Sentinel流控

1.3.5、降级机制

降级其实就是为服务提供一个兜底方案,一旦服务无法正常调用,就使用兜底方案。

SpringCloud实践系列(五):Sentinel流控

1.4、常见容错组件

HystrixResilience4JSentinel
Hystrix是由Netflflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Resilicence4J一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystrix官方推荐的替代产品。不仅如此,Resilicence4j还原生支持Spring Boot 1.x/2.x,而且监控也支持和prometheus等多款主流产品进行整合。Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。

SpringCloud实践系列(五):Sentinel流控

1.5、Sentinel

1.5.1、什么是Sentinel

Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控、熔断降级、系统负载保护等多个维度来保护服务的稳定性。

1.5.2、Sentinel特点

SpringCloud实践系列(五):Sentinel流控

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

1.5.3、Sentinel组成部分

Sentinel分为两部分:

  1. 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo /Spring Cloud 等框架也有较好的支持。
  2. 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

1.5.4、Sentinel架构

SpringCloud实践系列(五):Sentinel流控

【API调用】常见api

# 接口概览
http://localhost:8720/api
# 获取资源的metrics信息
http://localhost:8720/cnode?id=接口路径
# 获取流控规则接口
http://localhost:8720/getRules?type=flow
# 设置规则接口
http://localhost:8720/setRules

二、安装部署Dashboard

下载jar包

github.com/alibaba/Sen…

启动控制台

# 直接到jar包目录下,使用jar命令启动项目(控制台本身是一个SpringBoot项目) 
java -Dserver.port=8088 -Dcsp.sentinel.dashboard.server=localhost:8088 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar

页面访问

# http://localhost:8088/#/login
# 默认用户名密码是 sentinel/sentinel

SpringCloud实践系列(五):Sentinel流控

三、快速开始

  • 添加依赖

    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
  • 配置文件

    spring:
      application:
        name: cloud-order # 服务名
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # 指定nacos-server的地址
            username: nacos
            password: nacos
            namespace: dev
        sentinel:
          transport:
            port: 8719 # 默认8719(当前服务,对sentinel提供的,调用端口号,用于监控服务访问数据)
            dashboard: localhost:8888 # dashboard地址
          eager: true # 启动项目是自动注入到sentinel中
    server:
      port: 9003
    

启动项目,访问http://localhost:8088即可看到,已经注册进去了

四、Sentinel规则操作

【流控】:当满足被流控规则,控制流量 --- api层

【熔断降级】:当满足规则,走备用方案 --- api层

【热点】:细化到接口的参数,当携带指定参数时,对其做QPS限制 --- api的参数层

【系统规则】: 对整个服务做流控--- 整个服务层

【授权】:配置当前服务,那些服务能调用(白名单)、哪些服务不能调用(黑名单)。

SpringCloud实践系列(五):Sentinel流控

4.1、流控

【流控】:当满足被流控规则,控制流量 --- api层

SpringCloud实践系列(五):Sentinel流控

4.1.1、针对来源

针对某个服务做流控

default代表对所有服务的调用做流控

4.1.2、流控规则

  • QPS

    每秒请求数

  • 线程数

    同时请求的线程数量

4.1.3、流控模式

  • 直接(默认)

    接口达到限流条件时,开启限流

  • 关联

    当关联的资源达到限流条件时,开启限流 [适合做应用让步]

  • 链路

    当从某个接口,进入当前资源达到限流条件时,开启限流

4.1.4、流控效果

  • 快速失败(默认)

    直接失败,抛出异常,不做任何额外的处理,是最简单的效果

  • Warm Up

    它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景。

    eg:预热时长填10,QPS填900,那么如果一下进来900,会立即完成300,剩下的在10s内完成

  • 排队等待

    让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。

4.2、熔断降级

【熔断降级】:当满足规则,走备用方案 --- api层

4.2.1、慢调用比例

SpringCloud实践系列(五):Sentinel流控

上面配置表示

在【统计时长】内,请求数超过【最小请求数】,并且请求中响应时间大于【最大RT】的比例超过【比例阈值】,就会触发熔断,在【熔断时长内】直接走降级方法。

4.2.2、异常比例

SpringCloud实践系列(五):Sentinel流控

上面配置表示

在【统计时长】内,请求数超过【最小请求数】,并且异常比例超过【比例阈值】,就会触发熔断,在【熔断时长内】直接走降级方法。

4.2.3、异常数

SpringCloud实践系列(五):Sentinel流控

上面配置表示

在【统计时长】内,请求数超过【最小请求数】,并且异常数超过【异常数】,就会触发熔断,在【熔断时长内】直接走降级方法。

4.3、热点

【热点】:细化到接口的参数,当携带指定参数时,对其做QPS限制 --- api的参数层

SpringCloud实践系列(五):Sentinel流控

上面配置表示

请求该资源时,携带了第【参数索引】个参数,且在【统计窗口时长】内,QPS达到【单机阈值】则被流控

📢:资源需要使用注解@SentinelResource自定义才能生效

并且,由于使用了该注解,抛出的异常ParamFlowException,需要自己捕获

4.4、系统规则

【系统规则】: 对整个服务做流控--- 整个服务层

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

  • 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 达到阈值即触发系统保护。
SpringCloud实践系列(五):Sentinel流控

4.5、授权

【授权】:配置当前服务,那些服务能调用(白名单)、哪些服务不能调用(黑名单)。

SpringCloud实践系列(五):Sentinel流控

上面配置表示

/auth/login接口,不允许cloud-order和cloud-jifen这两个服务调用

五、@SentinelResource及异常捕获

5.1、@SentinelResource使用

5.1、使用规则

【作用】

1、用在方法上,用于自定义资源名

2、在设置热点时,必须使用该注解自定义的资源名,不然不生效

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。

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

5.2、使用示例

使用@SentinelResource对流控、降级做自定义处理

  • 方式一:写在同一个类里

    @RestController
    @Slf4j
    public class AnnoController {
        
        @RequestMapping("/anno1")
        /**
         * 当访问资源出现【流控】,会进入 blockHandler 指定的方法去进行处理
         * 当访问资源出现 异常/【熔断降级】,会进入到 fallback 去进行处理
         */
        @SentinelResource(value = "anno1",
                					blockHandler="anno1BlockHandler",
                					fallback = "anno1Fallback")
        public String anno1(String name){
            if("wolfcode".equals(name)){
                throw new RuntimeException();
            }
            return "anno1";
        }
      
      	/**
         * 限流备用方法
         */
        public String anno1BlockHandler(String name, BlockException ex){
            log.error("{}", ex);
            return "接口被限流了";
        }
      
        
      	/**
         * 异常或降级备用方法
         */
        public String anno1Fallback(String name, Throwable throwable) {
            log.error("{}", throwable);
          	// 区分熔断降级和其他异常
          	if (throwable instanceof DegradeException) {
              return "熔断降级";
            }
            return "非sentinel异常";
        }
    }
    
  • 写到别的类中

    @RestController
    @Slf4j
    public class AnnoController {
        
        @RequestMapping("/anno1")
        /**
         * 当访问资源出现【限流】,会进入 BlockExceptionHandler类的anno1BlockHandler方法
         */
        @SentinelResource(value = "anno1",
                					blockHandler="anno1BlockHandler",
                					blockHandlerClass="BlockExceptionHandler",
                          fallback = "anno1Fallback",
                          fallbackClass = "DegradeExceptionHandler"
        )
        public String anno1(String name){
            if("wolfcode".equals(name)){
                throw new RuntimeException();
            }
            return "anno1";
        }
    }
    
    • 限流备用方法

      public class BlockExceptionHandler {
        	/**
           * 静态方法
        	*/
        	public static String anno1BlockHandler(String name, BlockException ex){
              log.error("{}", ex);
              return "接口被限流了";
          }
        
         public static String anno1Fallback(String name, Throwable throwable) {
              log.error("{}", throwable);
            	// 区分熔断降级和其他异常
            	if (throwable instanceof DegradeException) {
                return "熔断降级";
              }
              return "非sentinel异常";
          }
      }
      
    • 异常/降级备用方法

      public class DegradeExceptionHandler {
        	/**
           * 静态方法
        	*/
         public static String anno1Fallback(String name, Throwable throwable) {
              log.error("{}", throwable);
            	// 区分熔断降级和其他异常
            	if (throwable instanceof DegradeException) {
                return "熔断降级";
              }
              return "非sentinel异常";
          }
      }
      

5.2、全局异常捕获

给每个方法都写一个对应的兜底方法,效率太低了

可以使用全局异常捕获对所有方法做兜底

5.2.1、Sentinel异常

SpringCloud实践系列(五):Sentinel流控

5.2.2、全局异常处理

@Slf4j
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    /**
     * 流控处理
  	*/
    @ExceptionHandler(FlowException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody // 该注解用于把对象转为json
    public OperationInfo handlerFlowException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        log.info("{}, 流控, {}", request.getRequestURI(), LogUtil.getStack(ex));
        return OperationInfo.failure("流控");
    }
  
  
  	/**
     * 熔断降级处理
  	*/
    @ExceptionHandler(DegradeException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public OperationInfo handlerDegradeException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        log.info("{}, 熔断降级, {}", request.getRequestURI(), LogUtil.getStack(ex));
        return OperationInfo.failure("熔断降级");
    }
  
  	// 其他略。。。。。。
}

六、规则持久化

SpringCloud实践系列(五):Sentinel流控

6.1、默认模式

【规则】:如果不做任何修改,Dashboard的推送规则方式是通过API将规则推送至客户端,并直接更新到内存中

【好处】:简单、无依赖

【坏处】:应用重启规则会消失,仅用于测试,不能用于生产环境

SpringCloud实践系列(五):Sentinel流控

6.2、Pull模式

【规则】

FileRefreshableDataSource定时从指定文件中读取规则JSON文件【本地文件】,如果发现文件发生变化,就更新规则缓存

FileRefreshableDataSource接收控制台规则推送,并根据配置,修改规则JSON【本地文件】

【优点】

能持久化配置的规则

【缺点】

服务部署在多台服务器上,无法共享本地文件

SpringCloud实践系列(五):Sentinel流控

6.3、Push模式

【规则】

将规则持久化在配置中心中,Sentinel把规则发送到配置中心,各个服务从配置中心中拿对应的规则

SpringCloud实践系列(五):Sentinel流控

6.3.1、Sentinel Dashboard代码改造

  • Sentinel Dashboard默认是往Sentinel客户端发送,需要修改为往 配置中心发送

    修改源码之后,重新打成jar包使用

6.3.2、微服务端

在要做Sentinel控制的服务里面配置

  • 添加依赖

    <!-- sentinel -->
    <dependency>
    	<groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!--sentinel的nacos持久化-->
    <dependency>
    	<groupId>com.alibaba.csp</groupId>
      <artifactId>spring-datasource-nacos</artifactId>
    </dependency>
    
  • 添加配置

    spring:
      cloud:
        nacos:
          config:
            server-addr: localhost:8848
            namespace: dev
            group: DEFAULT_GROUP
            prefix: cloud-order
            file-extension: yml
            username: nacos
            password: nacos
        sentinel:
          transport:
            port: 8730 # 默认8719(当前服务对sentinel提供访问的端口号)
            dashboard: localhost:8088 # dashboard地址
          eager: true # 启动项目是自动注入到sentinel中
          web-content-unify: false
          datasource:
            flow:
              nacos:
                server-addr: {nacos.server-addr}
                username: {nacos.username}
            		password: {nacos.password}
            		namespace: {nacos.namespace}
            		groupId: DEFAULT_GROUP
                dataId: ${spring.application.name}-flow-rules
                data‐type: json
                rule‐type: flow
            degrade:
            	nacos:
                server-addr: {nacos.server-addr}
                username: {nacos.username}
            		password: {nacos.password}
            		namespace: {nacos.namespace}
            		groupId: DEFAULT_GROUP
                dataId: ${spring.application.name}-degrade-rules
                data‐type: json
                rule‐type: degrade
            param-flow:
              nacos:
                server-addr: {nacos.server-addr}
                username: {nacos.username}
            		password: {nacos.password}
            		namespace: {nacos.namespace}
            		groupId: DEFAULT_GROUP
                dataId: ${spring.application.name}-param-flow-rules
                data‐type: json
                rule‐type: param-flow
            system:
            	nacos:
                server-addr: {nacos.server-addr}
                username: {nacos.username}
            		password: {nacos.password}
            		namespace: {nacos.namespace}
            		groupId: DEFAULT_GROUP
                dataId: ${spring.application.name}-system-rules
                data‐type: json
                rule‐type: system
            authority:
              nacos:
                server-addr: {nacos.server-addr}
                username: {nacos.username}
            		password: {nacos.password}
            		namespace: {nacos.namespace}
            		groupId: DEFAULT_GROUP
                dataId: ${spring.application.name}-authority-rules
                data‐type: json
                rule‐type: authority
    nacos:
    	server-addr: localhost:8848
    	username: nacos
    	password: nacos
    	namespace: sentinel