# Sentinel

# 概述

  • 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点, 从流量控制、熔断降级、系统自适应保护等多个维度来保障微服务的稳定性。
  • Sentinel 目前提供 Java、Go、C++ 等语言的原生支持。
  • Sentinel 提供Dashboard,支持规则配置持久化。
  • 官网文档:https://sentinelguard.io/zh-cn/docs/introduction.html (opens new window)

# 基本概念

# 资源

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容, 例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务, 甚至可以是一段代码。

# 规则

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

# 控制台

# 配置项

#nacos服务器地址
sentinel.datasource.nacos.server-addr=http://127.0.0.1:8848
#nacos服务器命名空间
sentinel.datasource.nacos.namespace=demo

# 启动端口、服务名等
server.port=8080
spring.application.name=hos-sentinel-dashboard-starter
debug=true

sentinel-dashboard.png

# 实时监控

实时监控汇总资源信息,同时,同一个服务下的所有机器的簇点信息会被汇总,并且秒级地展示在"实时监控"下。

# 簇点链路

  • 簇点链路中显示刚刚调用的资源,簇点链路页面实时的去拉取指定客户端资源的运行情况。 它一共提供两种展示模式:一种用树状结构展示资源的调用链路,另外一种则不区分调用链路展示资源的运行情况。
  • 簇点链路页面可以快捷的创建流控、熔断、热点、授权规则

注意:簇点监控是内存态的信息,它仅展示启动后调用过的资源。

sentinel-zdll.png

# 流控规则

sentinel-flow-rules.png

  • 单机阀值:表示触发规则的上限值

  • 流控模式

    • 直接:接口达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就可以限流),即当从某个接口过来的资源达到限流条件时,开启限流;
  • 流控效果

    • 快速失败:直接失败,就异常
    • Warm Up:达到峰值的启动时间。当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。 即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。 即 warm ups 时间
    • 排队等待:让请求以均匀的请求速度通过,阈值类型必须为QPS,否则无效请求达到阈值时,执行等待,等待时间为设置的超时时间
  • 集群模式

    • 单机均摊:每个单机的上限
    • 总体阀值:所有机器的上限

# 熔断规则

sentinel-degrade-rules.png

  • 某个资源出现不稳定状态(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败 避免影响到其他的资源而导致级联错误。

  • 熔断策略 - 慢调用比例

    当统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内
    请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个
    请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
    
    • 最大RT:最大响应时间,表示请求接口内部的处理时间,请求的响应时间大于该值则统计为慢调用
    • 比例阈值:慢调用的百分比,0 到 1 的范围。
  • 熔断策略 - 异常比例

      当统计时长内请求数目大于设置的最小请求数目,并且异常总数超过 异常数 时,资源进入降级状态
    
  • 熔断策略 - 异常数

      当统计时长内请求数目大于设置的最小请求数目,并且异常总数超过 异常数 时,资源进入降级状态
    

# 热点规则

sentinel-param-flow-rules.png

  • 使用QPS 模式对参数进行限制。
    • 参数索引:表示请求参数是第几个,不是参数实际值,是参数在所有参数的索引位置,从0开始

# 系统规则

sentinel-system-rules.png

  • 系统保护规则是从应用级别的入口流量进行控制,系统保护规则是应用整体维度的,而不是资源维度的。
  • 系统规则支持一下的模式:
    • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load(1分钟平均负载) 作为启发指标,进行自适应系统保护。 当系统 load(1分钟平均负载) 超过设定的启发值(阈值),且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。 系统容量由系统的 maxQps(秒级统计的最大QPS) * minRt(秒级统计的最小响应时间) 估算得出。设定参考值一般是 CPU cores * 2.5。

    • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

    • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。

    • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。

    • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

# 授权规则

sentinel-auth-rules.png

  • 授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。
    • 白名单:来源(origin)在白名单内的调用者允许访问
    • 黑名单:来源(origin)在黑名单内的调用者不允许访问

# 集群流控

  • 某个sentinel客户端采用集群的访问部署,在集群流控中,可以选择某台机器作为server端,用于流控统计并与其他机器进行交互。
  • 集群流控中共有两种身份:
    • Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
    • Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。

sentinel-cluster.png sentinel-cluster-server.png

# 机器列表

显示某个sentinel客户端所有的机器信息

sentinel-machines.png

# 网关规则

  • 网关规则有流控、熔断、系统三种规则类型,这里主要介绍网络流控规则,其他两种规则与非网关的流控、熔断规则一样。
  • 网关提供两种资源维度的限流
    • route维度:即在配置文件中配置的路由条目,资源名为对应的 routeId,这种属于粗粒度的限流,一般是对某个微服务进行限流。
    • 自定义API维度:用户可以利用 Sentinel 提供的API来自定义一些API分组,这种属于细粒度的限流,针对某一类的uri进行匹配限流,可以跨多个微服务。

sentinel-gateway.png

# 网关流控规则

  • 以routeId限流时,直接在 请求链路 里选择某个routeId即可,设置类似 流控规则
  • 以API分组限流时,需要先创建API分组(API管理 中 创建分组,创建分组时,可以选择精确、模糊、正则匹配3种方式) 在流控规则里使用API分组的模式进行限流,选择创建的API分组即可。
    • API分组:

        用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。 
        比如我们可以定义一个 API 叫 my_api,请求 path 模式为 /foo/** 和 /baz/** 的都 
        归到 my_api 这个 API 分组下面。 限流的时候可以针对这个自定义的 API 分组维度进行 限流
      

sentinel-gateway-flow.png sentinel-gateway-api.png

间隔:是指可以设置QPS的秒数,当间隔设置成2时,此时表示2秒内执行N次。 burst size: 表示宽容次数,如设置1,表示在一秒内,可以访问3次,3次以上则控流。

# 客户端配置

使用Sentinel进行流量控制时,需要引入相关配置,此时应用程序可以看作是一个Sentinel的客户端程序。

# 引入依赖

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

 <!--   nacos持久化sentinel     -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.4</version>
</dependency>

网关依赖

<!--   sentinel 整合 spring cloud gateway     -->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!-- sentinel 整合spring cloud alibaba -->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

# 配置项

# yml配置文件

spring:
  cloud:
    # sentinel 相关配置
    sentinel:
      transport:
        # sentinel-dashboard 控制台地址
        dashboard: 127.0.0.1:8080
        # 本应用(sentinel应用客户端)与 sentinel-dashboard 的交互端口,默认8719,如果被占用会自动加1
        port: 8719
      # 本应用监听nacos上的sentinel规则
      datasource:
        # 流控--dsl-flow允许自定义,没有固定要求,主要是里面的配置
        dsl-flow:
          # 持久化规则,从nacos中获取
          nacos:
            # nacos 地址
            server-addr: ${ENV_NACOS_ADDERSS:127.0.0.1:8848}
            # 持久化规则文件在nacos上的名称,必须是应用名-flow-rules
            dataId: ${spring.application.name}-flow-rules
            # 持久化规则文件在nacos上的分组
            groupId: SENTINEL_GROUP
            # 持久化规则文件在nacos上的命名空间
            namespace: ${ENV_DIS_NAMESPACE:demo}
            # 持久化规则文件在nacos上的文件格式
            data-type: json
            # 规则类型 flow-流控控制
            rule-type: flow
        # 熔断降级
        dsl-degrade:
          nacos:
            server-addr: ${ENV_NACOS_ADDERSS:127.0.0.1:8848}
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            namespace: ${ENV_DIS_NAMESPACE:demo}
            data-type: json
            rule-type: degrade
        # 热点规则
        dsl-param-flow:
          nacos:
            server-addr: ${ENV_NACOS_ADDERSS:127.0.0.1:8848}
            dataId: ${spring.application.name}-param-flow-rules
            groupId: SENTINEL_GROUP
            namespace: ${ENV_DIS_NAMESPACE:demo}
            data-type: json
            rule-type: param-flow
        # 系统规则
        dsl-system:
          nacos:
            server-addr: ${ENV_NACOS_ADDERSS:127.0.0.1:8848}
            dataId: ${spring.application.name}-system-rules
            groupId: SENTINEL_GROUP
            namespace: ${ENV_DIS_NAMESPACE:demo}
            data-type: json
            rule-type: system
        # 授权规则
        dsl-authority:
          nacos:
            server-addr: ${ENV_NACOS_ADDERSS:127.0.0.1:8848}
            dataId: ${spring.application.name}-authority-rules
            groupId: SENTINEL_GROUP
            namespace: ${ENV_DIS_NAMESPACE:demo}
            data-type: json
            rule-type: authority

  • dataId 以当前服务名称${spring.application.name}-后缀组成,后缀固定,分别为 流控规则(flow-rules)、熔断规则(degrade-rules)、热点规则(param-flow-rules)、 系统规则(system-rules)、授权规则(authority-rules)
  • rule-type 固定,具体可以查看 com.alibaba.cloud.sentinel.datasource.RuleType 类

# 网关yml配置文件

spring:
  cloud:
    # 网关配置
    gateway:
      enabled: true
      discovery:
        locator:
          lower-case-service-id: true
      routes:
        - id: mamagecenter-gateway-provider
          uri: lb://hos-sentinel-service
          predicates:
            - Path=/sentinel/gateway/{value}

    # sentinel 相关配置
    sentinel:
      transport:
        # sentinel-dashboard 控制台地址
        dashboard: 127.0.0.1:8080
        # 本应用(sentinel应用客户端)与 sentinel-dashboard 的交互端口,默认8719,如果被占用会自动加1
        port: 8719
      # 本应用监听nacos上的sentinel规则
      datasource:
      # 网关routeId规则
        dsl-gateway-flow:
          nacos:
            server-addr: ${ENV_NACOS_ADDERSS:127.0.0.1:8848}
            dataId: ${spring.application.name}-gateway-flow-rules
            groupId: SENTINEL_GROUP
            namespace: ${ENV_DIS_NAMESPACE:demo}
            data-type: json
            rule-type: gw-flow
      # 网关API分组规则
        dsl-gateway-api:
          nacos:
            server-addr: ${ENV_NACOS_ADDERSS:127.0.0.1:8848}
            dataId: ${spring.application.name}-gateway-api-rules
            groupId: SENTINEL_GROUP
            namespace: ${ENV_DIS_NAMESPACE:demo}
            data-type: json
            rule-type: gw-api-group

  • dataId 以当前服务名称${spring.application.name}-后缀组成,后缀固定,分别为 网关RouteId(gateway-flow-rules)、网关API分组(gateway-api-rules)
  • rule-type 固定,具体可以查看 com.alibaba.cloud.sentinel.datasource.RuleType 类

  • 注意:spring.cloud.sentinel.eager 属性,如果设置成true,会在网关请求之前就会加到控制台,会展示成非网关的客户端页面

# 使用示例

示例较为简单,主要在规则配置以及测试触发方面,这里主要展示Sentinel资源设置以及 触发控制规则后的统一异常处理。

    @GetMapping("/sentinel/system")
    public String sentinelSystem() {
        return "默认请求接口即为限流埋点===sentinel规则 ==== now time:" + LocalDateTime.now();
    }

Sentinel 默认为所有的 HTTP 服务提供了限流埋点。引入依赖后自动完成所有埋点。只需要再控制配置限流规则即可

    // 原本的业务方法.
    @SentinelResource(blockHandler = "blockHandlerForGetUser")
    public User getUserById(String id) {
        throw new RuntimeException("getUserById command failed");
    }
    
    // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
    public User blockHandlerForGetUser(String id, BlockException ex) {
        return new User("admin");
    }

使用@SentinelResource注解对某个特定的方法进行限流或降级处理

# 统一异常处理

# 默认统一处理

  • 业务项目pom.xml文件
    <!--   引入hos-sentinel-starter     -->
    <dependency>
        <groupId>com.mediway.hos</groupId>
        <artifactId>hos-framework-sentinel-starter</artifactId>
    </dependency>

# 业务自定义统一处理

  • 根据本身业务自定义流控异常统一处理,项目pom.xml设置
    <!--   引入alibaba-sentinel 依赖    -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>2.2.3.RELEASE</version>
    </dependency>

    <!--   nacos持久化sentinel     -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
        <version>1.8.0</version>
    </dependency>

  • 根据本身业务自定义流控异常统一处理,可以参考此类,主要是需要实现BlockExceptionHandler接口, 重写handle方法。
/**
 * 自定义接口限流处理
 */
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception {
       JSONObject resultObj = new JSONObject();
       if (ex instanceof FlowException) {
           resultObj.put("code", 100);
           resultObj.put("msg", "BlockExceptionHandler==接口限流");
       }
       if (ex instanceof DegradeException) {
           resultObj.put("code", 101);
           resultObj.put("msg", "BlockExceptionHandler==服务降级");
       }
       if (ex instanceof ParamFlowException) {
           resultObj.put("code", 102);
           resultObj.put("msg", "BlockExceptionHandler==热点参数限流");
       }
       if (ex instanceof SystemBlockException) {
           resultObj.put("code", 103);
           resultObj.put("msg", "BlockExceptionHandler==触发系统保护规则");
       }
       if (ex instanceof AuthorityException) {
           resultObj.put("code", 104);
           resultObj.put("msg", "BlockExceptionHandler==授权规则不通过");
       }

       BaseResponse baseResponse = BaseResponse.error(resultObj.getString("code"), resultObj.getString("msg"));
       response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
       response.setCharacterEncoding("UTF-8");
       response.setContentType("application/json; charset=utf-8");
       ObjectMapper objectMapper = new ObjectMapper();
       response.getWriter().println(objectMapper.writeValueAsString(baseResponse));
    }
}

# fallback

  • 资源使用@SentinelResource标识,并设置限流处理fallback参数。
  • fallback:针对Java本身出现的异常进行处理的对应属性。
  • 自定义fallback函数,必须与资源方法具有相同的参数,可以额外多一个Throwable参数。
  • 自定义fallback函数,如果不与资源方法在同一个类中,必须定义成static。
    // fallback:此方法异常(非限流异常)会调用 AnnotationFallbackHandlerException#fallBackA方法
    @GetMapping("/fallback")
    @SentinelResource(fallback = "fallBackA", fallbackClass = AnnotationFallbackHandlerException.class)
    public String testFallback() {
        return "异常统一处理接口 === fallback ==== now time:" + LocalDateTime.now();
    }

    /**
     * 在 @SentinelResource配置fallback、fallbackClass 值
     * 方法必须是 static
     */
    public class AnnotationFallbackHandlerException {
        // fallback函数,必须与资源方法入参一样,可以额外多一个Throwable
        public static String fallBackA(Throwable exception) {
            return "AnnotationFallbackHandlerException ====  全局(fallBackA)异常 === :" + exception.getMessage();
        }
}

# blockHandler

  • 资源使用@SentinelResource标识,并设置限流处理blockHandler参数。
  • blockHandler:针对违反Sentinel控制台配置规则时触发BlockException异常时对应处理的属性。
  • 自定义blockHandler函数,必须与资源方法具有相同的参数、返回类型,可以额外多一个BlockException参数。
  • 自定义blockHandlerk函数,如果不与资源方法在同一个类中,必须定义成static。
    @GetMapping("/blockHandler")
    @SentinelResource(blockHandler = "handlerExceptionA", blockHandlerClass = {AnnotationBlockHandlerException.class})
    public String testBlockHandler() {
        return "异常统一处理接口 === blockHandler ==== now time:" + LocalDateTime.now();
    }


    /**
     * 在 @SentinelResource配置blockHandler、blockHandlerClass 值
     * 方法必须是 static
     */
    public class AnnotationBlockHandlerException {
        /**
         * blockHandler函数, 返回类型和参数必须与原函数返回类型和参数一致,可以额外多一个BlockException
         */
        public static String handlerExceptionA(BlockException exception){
            return "AnnotationBlockHandlerException ====  全局(handlerExceptionA)异常 === :" + exception.getMessage();
        }
    }

# 授权规则

  • 使用授权规则时, 可以配置请求来源,从而定义不同的请求来源的应用名,便于规则配置。
  • 示例以从请求头header中获取origin,实际需要根据业务自定义
  • 需要实现RequestOriginParser接口
@Component
public class AuthorityRequestOriginParser implements RequestOriginParser {
    // 授权规则---请求来源处理
    @Override
    public String parseOrigin(HttpServletRequest request) {
        // 1.获取请求头
        String origin = request.getHeader("origin");
        // 2.非空判断
        if (StringUtils.isEmpty(origin)) {
            origin = "testAuth";
        }
        return origin;
    }
}