0%

【SpringCloudAlibaba】Sentinel实现熔断和限流

Sentinel用于服务降级,熔断,限流,类似Hsytrix


1 Sentinel介绍

1.1 简介

  • Sentinel(哨兵):功能类似我们之前学习的hystrix

1.2 下载和安装

  • 下载的是一个jar包,直接通过java -jar jar包名即可
    • 但默认启动的端口是8080,要保证该端口没有被占用
    • 也可以通过java -Dserver.port=端口号 -jar jar包名,来修改sentinel的启动端口

1.3 使用


2 初始化演示工程

  • (1)创建项目cloudalibaba-sentinel-service8401,此项目注册到nacos,并被sentinel保护
  • (2)依赖引入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!--web-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- web监控 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- nacos 服务发现 -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- sentinel 服务降级 -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
  • (3)修改配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    server:
    port: 8401

    spring:
    application:
    name: cloudalibaba-sentinel-service
    cloud:
    nacos:
    discovery:
    server-addr: localhost:8848 # nacos服务地址
    sentinel:
    transport:
    dashboard: localhost:8080 # sentinel服务地址
    port: 8719 # 假如端口被占用,自动从8719开始,依次+1,直至未被占用

    # 暴露服务信息
    management:
    endpoints:
    web:
    exposure:
    include: "*"
  • (4)主启动类
    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableDiscoveryClient //注册到nacos
    public class SentinelServiceMain8401 {
    public static void main(String[] args) {
    SpringApplication.run(SentinelServiceMain8401.class, args);
    }
    }
  • (5)Controller层
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @RestController
    public class FlowLimitController {

    @GetMapping("/testA")
    public String testA() {
    return "---- testA";
    }

    @GetMapping("/testB")
    public String testB() {
    return "---- testB";
    }
    }
  • (6)测试
    • 项目启动,因为sentinel使用懒加载,需要访问接口后,才会显示监控对象

3 流控规则

3.1 基本介绍

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

3.2 流控模式

  • (1)QPS-直接-快速失败(默认)
  • (2)线程数-直接-快速失败
    • 当请求的线程数超过阈值,则会进行报错
  • (3)关联
    • 当关联的资源达到阈值是,就限流自己
    • 同一个服务,不同请求路径,其中B路径请求达到阈值,但是A进行限流
    • 即B惹事,A挂了

3.3 流控效果

  • (1)直接失败
  • 前面出现过不多赘述
  • 有兴趣可查看源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
  • (2)warm up(预热)
  • 平时网址访问量很少,突然一瞬间访问量激增,为了不让服务器崩溃,需要逐步提升访问量,然后再达到最大值,形成循环渐进的效果
  • 公式:初始阈值:阈值/冷加载因子(默认3),一旦访问量达到预热启动值,在达到设置的预热时间后,阈值达到最大值
  • 效果:刚开始阈值为初始阈值,比较低,会出现报错。但系统也因此提高阈值,慢慢的阈值边到最大,就不会报错
  • (3)匀速排队
  • 阈值类型只能选择QPS
  • 控制请求通过的是渐渐间隔,避免突然一秒有大量请求,达到了阈值,只能直接报错。但排队不会直接报错,而是限定了当前秒内只能通过多少个请求,其余线程在后面稍等,当请求等待时间超过我们设置的时间,则返回限流信息

4 降级规则

4.1 基本介绍

  • 慢调用比例
  • 慢调用,请求的响应时间大于最大响应时间。
  • 当单位时长内,请求数大于设置的最小请求数,且慢调用比例大于阈值,则进行熔断。经过熔断时长后进入半开放状态。下一个请求响应时间小于阈值,结束熔断,否则继续熔断
  • 异常比例(秒级)
  • QPS >= 5 且异常比例(秒级统计)超过阈值,触发降级(熔断);时间窗口(熔断时长)结束后,进入半开放阶段,若请求成功,关闭降级(熔断),否则继续
  • 异常数(分钟级)
  • 异常数(分钟统计)超过阈值,触发降级(熔断);时间窗口(熔断时长)结束后,进入半开放阶段,若请求成功,关闭降级(熔断),否则继续

4.2 慢调用比例

4.3 异常比例

4.4 异常数


5 热点规则

5.1 基本介绍

  • 热点参数限流,会统计请求中传入的参数,若此参数为热点参数,则对此请求进行限流

5.2 代码演示

  • (1)Controller层
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @GetMapping("/hotKey")
    @SentinelResource(value = "hotKey", blockHandler = "deal_hotKey") //value: 唯一标识,一般与路径相同,blockHandler:服务降级方法
    public String hotKetTest(@RequestParam(value = "p1", required = false) String p1,
    @RequestParam(value = "p2", required = false) String p2) {

    return "--- hot-key-test";
    }

    public String deal_hotKey(String p1, String p2, BlockException blockException) { //参数要一致,并且需要BlockException参数
    return "你正在访问的是热点资源,请稍后访问!";
    }
  • (2)配置热点

5.3 参数例外项

  • 有时候,我们希望热点参数的特定值,可以有不同的阈值,来进行分配

5.4 问题提出

  • @SentinelResource 的服务降级方法,仅对于sentinel-web界面配置的内容进行服务降级,例如QPS是否达到阈值之类;但不会对Java运行异常进行服务降级,后面会教如何解决

6 系统规则

  • Sentinel系统自适应限流从整体维度对应用入口流量进行控制
阈值类型 说明
LOAD 仅对Linux/Unix-like机器生效,当系统的load超过启发值,且系统当前的并发线程超过估算的系统容量才会触发系统保护
RT 当单台机器上所有入口流浪的平均RT达到阈值触发保护机制
线程数 所有入口流量的并发线程数达到阈值触发保护机制
入口QPS 当所有入口流量的QPS达到阈值触发保护机制
CPU使用率 当系统CPU使用率超过阈值触发系统保护机制

7 @SentinelResource注解

7.1 资源名限流 和 url限流

  • (1)按资源名限流
  • @SentinelResource能给限流方法起一个资源名,如果以资源名来限流,系统不会调用默认的限流返回方法,会直接页面报错,所以在使用的时候要配置限流返回方法
  • @SentinelResource(value = "资源名", blockHandler = "限流返回方法名")
  • 限流返回方法的参数要与原方法参数一样,并额外多出BlockException类型的参数
  • (2)按url限流
  • 按url地址进行限流,系统会调用默认的限流返回方法,就算配置了@SentinelResource也是调用默认的限流返回方法,但是默认的限流返回方法,没有任何重要的消息,不太实用

7.2 限流返回方法与业务逻辑代码分离

  • (1)创建一个自定义的处理类
    1
    2
    3
    4
    5
    6
    public class CustomerBlockHandler {

    public static String blockHandler1(BlockException blockException) { //必须为静态方法
    return "customerHandler:这是服务降级返回的信息!";
    }
    }
  • (2)Controller层引入服务降级方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RestController
    public class RateLimitController {

    @GetMapping("/handler")
    @SentinelResource(value = "handler",
    blockHandlerClass = CustomerBlockHandler.class, //自定义处理类
    blockHandler = "blockHandler1") //处理类中的处理方法
    public String handler() {
    return "业务与降级处理分离测试!";
    }
    }

7.3 fallback

  • fallback 与 blockHandler的区别
    • fallback处理的是Java运行的异常,而blockHandler是处理限流规则出现的异常
  • 使用例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @GetMapping("/getPayment")
    @SentinelResource(value = "fallback", fallback = "fallbackHandler")
    public String getPayment(@RequestParam(value = "p1", required = false) String p1) {
    if(p1 != null) {
    throw new RuntimeException("Java业务出现异常!,非限流问题!");
    }
    return "调用成功!";
    }

    public String fallbackHandler(String p1, Throwable e) {
    return e.getMessage();
    }
    • 正常不带参访问,没出现异常。但带p1参数进行方法,异常触发fallback处理方法,页面不会报错

7.4 其他参数

  • exceptionToIgnore,忽略异常
  • 例子:@SentinelResource(value = "fallback", fallback = "fallbackHandler", exceptionsToIgnore = {RuntimeException.class})
  • 当发生RuntimeException时,Sentinel会忽略此异常,不会为此异常寻找其服务降级方法,而是直接页面报错

8 Sentinel与OpenFeign整合

  • 除了使用Sentinel的@SentinelResource来实现服务降级,还可以使用OpenFeign来实现服务降级
  • (1)引入OpenFegin依赖
    1
    2
    3
    4
    5
    <!-- openfeign -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  • (2)修改yaml配置
    1
    2
    3
    feign:
    sentinel:
    enabled: true #开启sentinel对feign的支持
  • (3)主启动类

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients //使用Feign
    public class OrderMain84 {
    public static void main(String[] args) {
    SpringApplication.run(OrderMain84.class, args);
    }
    }
  • (3)Service层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Component
    @FeignClient(value = "cloudAlibaba-provider-payment", fallback = FeignPaymentFallback.class) //fallback为自定义服务降级类,会自动根据同一方法名来寻找服务降级方法
    public interface FeignPaymentService {

    @GetMapping("/getPayment")
    public String getPayment();
    }


    @Component
    public class FeignPaymentFallback implements FeignPaymentService{
    @Override
    public String getPayment() {
    return "这是Feign的服务降级方法!";
    }
    }
  • (4)Controller层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RestController
    public class OrderController {

    //----------------Feign----------------
    @Autowired
    private FeignPaymentService feignPaymentService;

    @GetMapping("/consumer/getFeignPayment")
    public String getFeignPayment() {
    return feignPaymentService.getPayment();
    }
    }

9 Sentinel持久化