20200523 尚硅谷2020最新版SpringCloud【笔记】5

19. SpringCloud Alibaba Sentinel实现熔断与限流

概述

GitHub

GitHub 中文文档

下载地址

Spring Cloud Alibaba 官方文档

类比于 Hystrix

Hystrix Sentinel
需要手工搭建监控平台 Hystrix Dashboard 单独一个组建,可以独立出来
没有一套 web 界面可以给我们进行更加细粒度化的配置,如流量控制、速率控制、服务熔断、服务降级 直接界面化的细粒度统一配置

解决服务使用中的各种问题:

  • 服务雪崩
  • 服务降级
  • 服务熔断
  • 服务限流

安装 Sentinel 控制台

Sentinel分为两个部分:

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

访问sentinel管理界面:

URL 为 http://localhost:8080,登录账号密码均为 sentinel

初始化演示工程

前提:

  • 启动 Nacos
  • 启动 Sentinel
  1. Module,cloudalibaba-sentinel-service8401

    扫描二维码关注公众号,回复: 11253805 查看本文章
  2. POM

    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
  3. YML

    server:
      port: 8401
    
    spring:
      application:
        name: cloudalibaba-sentinel-service
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 #Nacos服务注册中心地址
        sentinel:
          transport:
            dashboard: localhost:8080 #配置Sentinel dashboard地址
            port: 8719
    
    management:
      endpoints:
        web:
          exposure:
            include: '*'
    
  4. 主启动类

  5. 业务类

    @RestController
    @Slf4j
    public class FlowLimitController {
        @GetMapping("/testA")
        public String testA() {
            return "------testA";
        }
    
        @GetMapping("/testB")
        public String testB() {
            log.info(Thread.currentThread().getName() + "\t" + "...testB");
            return "------testB";
        }
    }
    
  6. 启动微服务 8401

  7. 访问请求,刷新 Sentinel,可以在控制台看到此请求

    Sentinel 只有在访问过一次请求后,才可以在控制台看到

流控规则

控制台中的【流控规则】

【流控规则】> >【新增流控规则】,字段说明:

  • 资源名:唯一名称,默认请求路径
  • 针对来源:Sentinel 可以针对调用者进行限流,填写微服务名,默认 default(不区分来源)
  • 阈值类型/单机阈值:
    • QPS(Queries-per-second,每秒钟的请求数量):当调用该 api 的 QPS 达到阈值的时候,进行限流
    • 线程数:当调用该 api 的线程数达到阈值的时候,进行限流
  • 是否集群:不需要集群
  • 流控模式:
    • 直接:api达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api 级别的针对来源 】
  • 流控效果:
    • 快速失败:直接失败,抛异常
    • Warm Up:根据 codeFactor(冷加載因子,默认3)的值,从阈值/codefactor,经过预热时长,才达到设置的 QPS 阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为 QPS,否则无效

流控模式

直接(默认)
QPS、直接、快速失败

默认模式

img

快速刷新访问 http://localhost:8401/testA,出现 Sentinel 提示:

Blocked by Sentinel (flow limiting)
线程数、直接

img

在业务代码中增加延时,之后操作如上,也会出现 Sentinel 提示

@GetMapping("/testA")
public String testA() throws InterruptedException {
    // 测试 线程数 直接
    TimeUnit.SECONDS.sleep(3);
    log.info(Thread.currentThread().getName() + "\t" + "...testA");

    return "------testA";
}
关联
是什么?
  • 当关联的资源达到阈值时,就限流自己
  • 当与A关联的资源B达到阈值后,就限流自己
  • B惹事,A挂了
QPS、关联、快速失败

img

是否流控 /testA 取决于 /testB 的 QPS,如果超过阈值,访问 /testA 会出现 Sentinel 提示信息,而 /testB 不受影响。

使 /testB QPS 超过阈值,可以使用 Postman 的 【Run Collection】 功能。

链路

多个请求调用了同一个微服务

流控效果

GitHub Wiki

直接->快速失败(默认的流控处理)

直接失败,抛出异常

Blocked by Sentinel (flow limiting)

源码

com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
预热

公式:阈值会经过变化,开始为阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

源码:

com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

案例:阈值为10,预热市场 5s

系统初始化阈值为 10/3,约等于 3,即阈值开始为 3;经过 5s 后阈值慢慢升高,恢复到 10

应用场景:

如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

排队等待

匀速排队,阈值必须设置为 QPS

源码:

com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

降级规则

GitHub Wiki

控制台中的【降级规则】

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致級联错误。当资源被降級后,在接下来的降級时间密口之内,对该资源的调用都自动熔断(默认行为是抛出 Degradeexception)。

Sentinel 的断路器是 没有半开状态

半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考 Hystrix

降级策略实战

img

  • RT(平均响应时间,秒级)

    平均响应时间 超出阈值在时间窗口内通过的请求 >=5 ,两个条件同时满足后触发降级

    窗口期过后关闭断路器

    RT 最大 4900(更大的需要通过 -Dcsp.sentinel.statistic.max.rt=XXX オ能生效)

  • 异常比列(秒级)

    QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

  • 异常数(分钟级)

    异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

RT

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

异常比例

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

异常数

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

时间窗口一定要大于等于60秒。

热点key限流

GitHub Wiki

控制台中的【热点规则】

img

承上启下

承接 Hystrix

兜底方法

分为系统默认和客户自定义,两种

之前的case,限流出问题后,都是用 sentinel 系统默认的提示:Blocked by Sentinel (flow limiting)

我们能不能自定?类似 Hystrⅸ,某个方法出问题了,就找对应的兜底降级方法?

HystrixCommand@SentinelResource

com.alibaba.csp.sentinel.slots.block.BlockException

@SentinelResource 只管 Sentinel 配置违规,不处理代码异常

代码

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1, @RequestParam(value = "p2", required = false) String p2) {
    //int age = 10/0;
    return "------testHotKey";
}

public String deal_testHotKey(String p1, String p2, BlockException exception) {
    return "------deal_testHotKey,o(╥﹏╥)o";  //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
}

如果使用了 @SentinelResource ,但是没有配置 blockHandler 属性,违反热点规则会出现错误页面,而不是 Sentinel 默认的错误提示

参数例外项

热点参数的注意点,参数必须是基本类型或者String

特殊情况

  • 普通

    超过1秒钟一个后,达到阈值1后马上被限流

  • 我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样

  • 特例

    假如当p1的值等于5时,它的阈值可以达到200

系统规则

GitHub Wiki

控制台中的【系统规则】

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 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 达到阈值即触发系统保护。

img

@SentinelResource

按资源名称限流+后续处理

配置资源名为 byResource 的流控规则

@GetMapping("/byResource")
@SentinelResource(value = "byResource", blockHandler = "handleException")
public CommonResult byResource() {
    return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, "serial001"));
}

public CommonResult handleException(BlockException exception) {
    return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
}

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
    return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
}

按照Url地址限流+后续处理

配置资源名为 /rateLimit/byUrl 的流控规则

没有配置 blockHandler,返回 Sentinel 默认的提示信息

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
    return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
}

上面兜底方法面临的问题

  1. 系统默认的,没有体现我们自己的业务要求。
  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  3. 每个业务方法都添加一个兜底的,代码膨胀加剧。
  4. 全局统一的处理方法没有体现。

客户自定义限流处理逻辑

  1. 创建 CustomerBlockHandler 类用于自定义限流处理逻辑

    必须是 static 方法

    public class CustomerBlockHandler {
        public static CommonResult handlerException2(BlockException exception) {
            return new CommonResult(4444, "按客戶自定义,global handlerException----2");
        }
    }
    
  2. 业务类中使用

    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
    public CommonResult customerBlockHandler() {
        return new CommonResult(200, "按客戶自定义", new Payment(2020L, "serial003"));
    }
    
  3. 测试限流

更多注解属性说明

GitHub Wiki

Sentinel 主要有三个核心 API:

  • SphU 定义资源
  • Tracer 定义统计
  • ContextUtil 定义了上下文

服务熔断功能

sentinel整合ribbon+openFeign+fallback

Ribbon系列

  1. Module

    • 生产者:cloudalibaba-provider-payment9003、cloudalibaba-provider-payment9004
    • 消费者:cloudalibaba-consumer-nacos-order84
  2. 测试 Ribbon 调用

    ## 直接调用生产者
    http://localhost:9003/paymentSQL/1
    http://localhost:9004/paymentSQL/1
    ## 调用消费者
    http://localhost:84/consumer/paymentSQL/1
    
  3. 测试 @SentinelResource 注解的 fallbackblockHandler 属性

    fallback 管运行异常,无 blockHandler 时,也管配置违规

    blockHandler 管配置违规

    exceptionsToIgnore 忽略异常

    @RequestMapping("/consumer/fallback/{id}")
    // @SentinelResource(value = "fallback") 
    // @SentinelResource(value = "fallback", fallback = "handlerFallback") 
    //@SentinelResource(value = "fallback",blockHandler = "blockHandler") 
    // @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
    // @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
    
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }
    
        return result;
    }
    
    //本例是fallback
    public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444, "兜底异常handlerFallback,exception类型=" + e.getClass().getCanonicalName() + ",exception内容  " + e.getMessage(), payment);
    }
    
    //本例是blockHandler
    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(), payment);
    }
    
    ## 触发 IllegalArgumentException
    http://localhost:84/consumer/fallback/4
    ## 触发 NullPointerException
    http://localhost:84/consumer/fallback/11
    
    • @SentinelResource(value = "fallback")

      出现错误页面

    • @SentinelResource(value = "fallback",fallback = "handlerFallback")

      管运行异常

      不会出现错误页面,加上流控规则后,违反规则后,会调用 handlerFallback 方法,但是异常类型为 com.alibaba.csp.sentinel.slots.block.flow.FlowException

    • @SentinelResource(value = "fallback",blockHandler = "blockHandler")

      不管运行异常,只管配置违规

    • @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")

      fallback 管运行异常

      blockHandler 管配置违规

    • @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})

      fallback 管运行异常,其中不管 IllegalArgumentException

      blockHandler 管配置违规

Feign系列

  1. YML

    feign:
      sentinel:
        enabled: true
    
  2. Feign 服务接口

    @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
    public interface PaymentService {
        @GetMapping(value = "/paymentSQL/{id}")
        public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
    }
    
  3. Feign 服务接口的 fallback 实现类

    @Component
    public class PaymentFallbackService implements PaymentService {
        @Override
        public CommonResult<Payment> paymentSQL(Long id) {
            return new CommonResult<>(44444, "服务降级返回,---PaymentFallbackService", new Payment(id, "errorSerial"));
        }
    }
    
  4. 测试

    关闭生产者应用,访问服务,发现返回的是 fallback 实现类中的响应

熔断框架比较

Sentinel Hystrix Resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(Leaparray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
限流 基于QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式、匀速器模式、预热排队模式 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 简单的监控查看 不提供控制台,可对接其他监控系统

规则持久化

一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效

  1. 修改cloudalibaba-sentinel-service8401
  2. POM
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  1. YML
spring:
  cloud:
    sentinel:
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow
  1. 在 Nacos 中添加配置,dataId 为 cloudalibaba-sentinel-service,groupId 为 DEFAULT_GROUP,配置内容为:
[
    {
        "resource": "/ratelimit/byUrl",
        "limitapp": "default",
        "grade": 1,
        "count": 5,
        "strategy": 0,
        "controlBehavior": 0,
        "clustermode": false
    }
]

​ resource:资源名称;

​ limitapp:来源应用;

​ grade:阈值类型,0 表示线程数,1 表示QPS;

​ count:单机阈值;

​ strategy:流控模式,0 表示直接,1 表示关联,2 表示链路;

​ controlBehavior:流控效果,0 表示快速失败,1表示 Warm Up,2 表示排队等待;

​ clusterMode:是否集群;

  1. 重启 8401,访问 http://localhost:8401/rateLimit/byUrl,查看 Sentinel 流控规则中存在 Nacos 中配置的规则

猜你喜欢

转载自www.cnblogs.com/huangwenjie/p/12942404.html