官方文档:https://github.com/alibaba/Sentinel

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

Sentinel 具有以下特征:

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

image.png

服务端启动

下载地址:https://github.com/alibaba/Sentinel/releases

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

访问地址:http://127.0.0.1:8080/
image.png

客户端配置

默认需要依赖 spring-cloud-alibaba-dependencies

  • 父 pom 依赖

    <dependencyManagement>
          <dependencies>
    
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-dependencies</artifactId>
                  <version>${spring-cloud.version}</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
    
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                  <version>2.2.1.RELEASE</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
    
          </dependencies>
      </dependencyManagement>
    
  • 客户端依赖

    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
  • 客户端配置

    spring:
    cloud:
      sentinel:
        transport:
          port: 8719
          dashboard: localhost:8080 # 服务端地址
    

    限流规则

image.png

阈值类型

  • QPS
    • 每秒访问的次数
  • 线程数
    • 可以支持多少线程提供资源访问

流控模式

  • 直接
    • 就是直接针对当前访问的资源
  • 关联
    • 当配置了关联资源的时候,如果当前访问资源超过了配置的阈值,那么被关联的资源将被限流
    • 如:根据库存的服务关联订单的服务,当库存达到阈值的时候,会限制订单,这样用户就不能访问订单了,从而从源头控制了流量
  • 链路
    • 当多个资源都需要访问一个资源的时候,可以根据链路设置流量控制

流控效果

  • 快速失败
    • 默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时
  • Warm Up
    • Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档
    • 阈值:有两个含义,正常情况下,阈值为所设置的 阈值 / 3 ,所设置的阈值为最大阈值
    • 预热时常:当超过正常阈值后,多少秒内,阈值会达到最大阈值数量
  • 排队等待(**匀速排队)**
    • 匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 流量控制 - 匀速器模式
    • 超时事件:指超过等待多久没有得到执行的请求,将会移除队列,直接报错

降级规则

我们通常用以下几种方式来衡量资源是否处于稳定的状态:

  • 平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限,可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

    • RT:一秒内 5 个请求的平均响应时间
    • 时间窗口:熔断后多少秒内恢复
  • 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

    • 异常比例:小数,[0.0, 1.0]
    • 时间窗口:熔断后多少秒内恢复
  • 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

    • 异常数:1 分钟之内异常的数量
    • 时间窗口:熔断后多少秒内恢复

注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。示例:

Entry entry = null;
try {
  entry = SphU.entry(key, EntryType.IN, key);

  // Write your biz code here.
  // <<BIZ CODE>>
} catch (Throwable t) {
  if (!BlockException.isBlockException(t)) {
    Tracer.trace(t);
  }
} finally {
  if (entry != null) {
    entry.exit();
  }
}

热点规则

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

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

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

  • 参数索引:从 0 开始,表示第一个参数
  • 单机阈值:QPS
  • 窗口时长:表示流量控制时间

要是用流控,必须使用 @SentinelResource 注解

@GetMapping("/backend")
@SentinelResource(value = "/backend", blockHandler = "blockBackend", blockHandlerClass = BlockHandler.class)
public String backend(String name) {
    System.out.println("name = " + name);
    return testService.test();
}

如果需要定义具体值进行热点限流的话,需要将普通限流数调大

image.png

系统规则

针对全局资源的控制,配置的时候系统默认规则要大于自定义的规则

image.png

  • 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 达到阈值即触发系统保护。

自定义异常信息

异常方法要和资源的方法参数签名一致,否则不生效

  • blockHandler:是针对流控
  • blockHandlerClass:流控处理类
  • fallback:正对内部异常
  • fallbackClass:内部异常处理累

单独配置

@GetMapping("/echo")
@SentinelResource(value = "/echo", blockHandler = "blockEcho", fallback = "fallbackEcho")
public String echo(String name) {
    System.out.println("echo = " + new Date());

    if (RandomUtils.nextBoolean()) {
        throw new RuntimeException("xxxxxxxxxxx");
    }

    return "echo " + name;
}

public String blockEcho(String name, BlockException ex) {
    return "进行了流量控制";
}

public String fallbackEcho(String name) {
    return "发生了内部异常";
}

全局配置(注意函数必须是 static 函数)

@GetMapping("/frontend")
@SentinelResource(value = "/frontend", blockHandler = "blockFrontend", blockHandlerClass = {BlockHandler.class})
public String frontend() {
    return testService.test();
}

public static class BlockHandler {
    public static String blockFrontend(BlockException ex) {
        return "进行了流量控制";
    }
}

配置持久化

使用 Nacos 持久化

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

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  • NacosWritableDataSource 的实现

    public class NacosWritableDataSource<T> implements WritableDataSource<T> {
    
      private String serverAddr, groupId, dataId;
    
      public NacosWritableDataSource(String serverAddr, String groupId, String dataId) {
          this.serverAddr = serverAddr;
          this.groupId = groupId;
          this.dataId = dataId;
      }
    
      @Override
      public void write(T t) throws Exception {
          String jsonConfigInfo = encodeJson(t);
          ConfigService configService = NacosFactory.createConfigService(serverAddr);
          boolean isPublishOk = configService.publishConfig(dataId, groupId, jsonConfigInfo);
          System.out.println("write to nacos is " + isPublishOk);
      }
    
      private String encodeJson(T t) {
          return JSON.toJSONString(t);
      }
    
      @Override
      public void close() throws Exception {
      }
    }
    
  • 实现:当系统启动时,会调用 init 方法实现配置的读取,然后将 NacosWritableDataSource 实例注册到 Sentinel 的监控系统中,当配置发生变化,会调用 NacosWritableDataSourcewrite 方法

    @Component
    public class DataSourceInitFunc {
    
      @Value("${spring.cloud.nacos.config.server-addr}")
      private String remoteAddress;
    
      @Value("${spring.cloud.nacos.config.group}")
      private String groupId;
    
      @Value("${spring.application.name}")
      private String dataId;
    
      @PostConstruct
      public void init() {
          ReadableDataSource<String, List<FlowRule>> flowRuleDataSource 
              = new NacosDataSource<>(remoteAddress, groupId, dataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
          FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
    
          WritableDataSource<List<FlowRule>> wds = new NacosWritableDataSource<>(remoteAddress, groupId, dataId);
          WritableDataSourceRegistry.registerFlowDataSource(wds);
      }
    }
    

使用 Redis 持久化

推模式:使用 Redis 配置规则
Sentinel 针对 Redis 作了相应适配,底层可以采用 Redis 作为规则配置数据源。使用时只需添加以下依赖:

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

Redis 动态配置源采用 Redis PUB-SUB 机制实现

整合 Feign

application.yml

feign:
  sentinel:
    enabled: true

关于降级

如果 sentinel 和 feign 都做了降级处理,默认会先走 sentinel 的降级逻辑,因为先走了 sentinel 的降级,请求变为正常,所以走不到 feign 的降级逻辑