分布式微服务流量控制Sentinel由浅入深

Sentinel简介

1.1 流量控制&熔断降级介绍

1.1.1 流量控制

举个例子,故宫每天限制有个八万人,超过八万人就买不到票,因为如果人数太多会影响景点的正常开放。这种只卖N张票的操作就是流量控制。

流量控制在网络中是一个常用的概念,它用于调整网络包的发送数据,在网络传输时,任意时间到来的请求往往时随机不可控的,而系统的处理能力是有限的,我们需要根据系统的处理能力对流量进行控制。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CN2DONcr-1626340668105)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\1625469096656.png)]

1.1.2 熔断降级简介

在调用系统的时候,如果调用链路中某个资源出现问题,最终会导致请求发生堆积

在这里插入图片描述

而熔断降级就可以解决这个问题,所谓的熔断降级就是当检测到调用链路中某个资源出现问题,例如请求时间过长,则对这个资源的调用进行限制,让请求快速失败,避免影响到其他资源而导致级联故障。

1.1.3 流量控制&熔断降级的实现方案

  • Hystrix:在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
  • Resilience4j :Resilience4j是一个轻量级容错框架,设计灵感来源于Netflix 的Hystrix框架,为函数式编程所设计。Resilience4j 提供了一组高阶函数(装饰器),包括断路器,限流器,重试,隔离,可以对任何的函数式接口,lambda表达式,或方法的引用进行增强,并且这些装饰器可以进行叠加。这样做的好处是,你可以根据需要选择特定的装饰器进行组合。在使用时,你不需要引入所有和Resilience4j相关的包,只需要引入所需要的即可。
  • Sentinel :Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

对比

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

1.2 Sentinel介绍

1.2.1 Sentinel简介

Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

  • Sentinel组成
    • 核心库:主要指java客户端,不依赖任何框架/库,能运行java 7及以上的版本的运行时环境,同时对Dubbo/Spring Cloud等框架也有较好的支持
    • 控制台:控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等

1.2.2 Sentinel特征

  • 丰富的应用场景:承接10年双十一大促场景,例如:秒杀、消息的削峰填谷、集群流量控制
  • 完备的实时监控:提供实时监控功能,在控制台可查看接入的单台机器的秒级数据
  • 广泛的开源生态:提供开箱即用的与其他开源框架整合模块
  • 完善的SPI扩展点:提供简单易用、完善的SPI扩展接口,可以通过实现接口快速定制逻辑

1.2.3 Sentinel特性

在这里插入图片描述

1.2.4 Sentinel开源生态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkWb1ov1-1626340668112)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\1625472651871.png)]

1.2.5 Sentinel 的相关概念

  • 资源:资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
  • 规则:围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

1.2.6 Sentinel的优势

  • 友好的控制面板
  • 支持实时监控
  • 支持多种限流,支持QPS\线程数等多种限流策略
  • 支持多种降级模式,平均返回时间降级、异常率降级
  • 方便扩展开发
  • 支持链路的关联,可实现链路统计限流、系统保护、热门资源保护等

Sentinel入门

2.1 Sentinel本地应用构建

  • 创建本地应用

    • 创建项目

    • 引入依赖

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      
      <dependency>
          <groupId>com.alibaba.csp</groupId>
          <artifactId>sentinel-core</artifactId>
          <version>1.8.2</version>
      </dependency>
      
    • 定义限流规则

      @RequestMapping(value = "/test")
      public String test(){
          //SphU.entry 启用限流
          try (Entry Entry = SphU.entry("Test")){
              return "你好,Sentinel";
          }catch (Exception e){
              return "系统繁忙!";
          }
      }
      
      //定义限流规则
      @PostConstruct
      public void initFlowRules(){
          //创建集合存放限流规则
          List<FlowRule> rules = new ArrayList<>();
          //创建限流规则
          FlowRule rule = new FlowRule();
          rule.setResource("Test"); //定义资源
          rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //定义限流规则类型
          rule.setCount(1); //每秒可通过的请求个数
          //存入集合
          rules.add(rule);
          //Sentinel加载集合
          FlowRuleManager.loadRules(rules);
      }
      
  • 搭建本地Sentinel控制台

    • 下载Sentinel控制台jar包:下载地址

    • 运行jar包

      java -Dserver.port=8088 -jar [jarname.jar]
      
    • 访问测试: http://localhost:8088

  • 本地应用接入本地Sentinel控制台

    • 引入依赖

      <dependency>
          <groupId>com.alibaba.csp</groupId>
          <artifactId>sentinel-transport-simple-http</artifactId>
          <version>1.8.2</version> <!-- 需要注意版本号 -->
      </dependency>
      
    • 配置本地应用的JVM参数

      -Dcsp.sentinel.dashboard.server=localhost:8088 -Dproject.name=SentinelDemo
      

    在这里插入图片描述

2.2 限流规则

Sentinel提供了两套设置限流规则的方式

  • 在应用中使用代码编写流控规则(之前案例展示)

    //定义限流规则
    @PostConstruct
    public void initFlowRules(){
        //创建集合存放限流规则
        List<FlowRule> rules = new ArrayList<>();
        //创建限流规则
        FlowRule rule = new FlowRule();
        rule.setResource("Test"); //定义资源
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //定义限流规则类型
        rule.setCount(1); //每秒可通过的请求个数
        //存入集合
        rules.add(rule);
        //Sentinel加载集合
        FlowRuleManager.loadRules(rules);
    }
    
  • 在Sentinel控制台设置流控规则

    在这里插入图片描述

2.3 Sentinel定义资源的方式

sentinel提供的定义资源的方式

  • 抛出异常的方式定义资源
  • 返回布尔值方式定义资源
  • 异步调用支持
  • 注解方式定义资源
  • 主流框架的默认适配

2.3.1 抛出异常的方式定义资源

Sentinel中的SphU包含了try-catch风格的API,用这种方式,当资源发生了限流之后会抛出BlockException。这个时候可以捕捉异常,进行限流之后的逻辑处理,关键代码如下:

@RequestMapping(value = "/test")
public String test(){
    //SphU.entry 启用限流
    try (Entry Entry = SphU.entry("Test")){
        return "你好,Sentinel";
    }catch (Exception e){
        return "系统繁忙!";
    }
}

2.3.2 返回布尔值方式定义资源

Sentinel中的SphO提供if-else风格的API,用这种方式,当资源发生限流之后会返回false这个时候可以根据返回值,进行限流之后的逻辑处理。

@RequestMapping(value = "/testBoo")
    public String test(){
        //SphO.entry 启用限流
        if(SphO.entry("TestBoo")){ //不存在限流
            try {
                return "你好,Sentinel";
            }finally { //保证SphO.entry需要和SphO.exit成对出现
                //SphO.entry需要和SphO.exit成对出现,
                //否则会影响到调用链记录,抛出异常
                SphO.exit();
            }
        }else{ //存在限流
            return "系统繁忙!";
        }

    }

2.3.3 异步调用支持

Sentinel支持异步调用链路的统计,在异步调用中,需要通过SphU.asyncEntry方法定义资源,并通常需要在异步的回调函数中调用exit方法

  • 开启异步支持

    @EnableAsync
    @SpringBootApplication
    public class SentinelApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SentinelApplication.class, args);
        }
    
    }
    
  • 创建异步方法

    @Async
    public void hello(){
        System.out.println("异步开始");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
        System.out.println("异步结束");
    }
    
  • 限流实现

    @Autowired
    private TestService testService;
    
    @RequestMapping(value = "/testAsync")
    public void test(){
        AsyncEntry asyncEntry = null;
        try {
            asyncEntry = SphU.asyncEntry("TestAsync"); //限流入口
            testService.hello(); //调用异步方法
        } catch (BlockException e) {
            System.out.println("系统繁忙!");
        } finally {
            if(null != asyncEntry)asyncEntry.exit(); //限流出口
        }
    }
    

2.3.4 注解方式定义资源

Sentinel支持注解@SentinelResource注解定义资源并配置blockHandler函数来进行限流之后的处理。

  • 引入依赖

    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-annotation-aspectj</artifactId>
        <version>1.8.2</version>
    </dependency>
    
  • 创建AspectJ的配置类

    @Configuration
    public class SentinelAspectConfig {
    
        @Bean
        public SentinelResourceAspect sentinelResourceAspect(){
            return new SentinelResourceAspect();
        }
    
    }
    
  • 实现流控

    @SentinelResource(value = "testRes",blockHandler = "exHandler")
    @RequestMapping("/testAspect")
    public String hello(){
        return "hello,Sentinel";
    }
    
    public String exHandler(BlockException e){
        System.out.println(e.getMessage());
        return "系统繁忙!";
    }
    

Sentinel高级

3.1 Spring Cloud整合Sentinel

  • 引入依赖

    <!-- Spring Cloud整合Sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>
    
  • 创建测试用例

    @SentinelResource(value = "testRes",blockHandler = "exHandler")
    @RequestMapping("/testAspect")
    public String hello(){
        return "hello,Sentinel";
    }
    
    public String exHandler(BlockException e){
        System.out.println(e.getMessage());
        return "系统繁忙!";
    }
    
  • 修改配置文件,配置接入的控制台

    #配置
    spring:
      application:
        name: SentinelDemo #项目名称
      cloud:
        sentinel:
          transport:
            dashboard: localhost:8088 #本地控制台
    

3.2 Feign整合Sentinel

  • 新建feign客户端

  • 引入依赖

    <!-- feign -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        <version>3.0.2</version>
    </dependency>
    
    <!-- Feign整合Sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>
    
  • 配置feign支持sentinel

    #配置
    spring:
      application:
        name: shop-feign-client
      cloud:
        #配置Sentinel
        sentinel:
          transport:
            dashboard: localhost:8088
    
    #Feign开启Sentinel
    feign:
      sentinel:
        enabled: true
    
    #eureka注册中心配置
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:10000/eureka/
    
    
  • 编写限流处理类

    @Component
    public class ProductServiceHandler implements ProductService {
    
        @Override
        public Object queryProduct() {
            return "系统繁忙!";
        }
    
    }
    
    @FeignClient(value = "shop-product-server",fallback = ProductServiceHandler.class)
    public interface ProductService {
    
        @GetMapping("/product/query")
        public Object queryProduct();
    
    }
    
  • 配置限流规则

    在配置中心配置限流规则,Feign配置限流资源名需要满足格式http请求方式:协议:服务器:路径及参数。例如 GET:http://localhost:8089/queryProduct

3.3 Spring Cloud Gateway整合Sentinel

  • 创建Gateway项目

  • 引入依赖

    <!-- 集成Sentinel实现Sentinel限流 -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        <version>1.8.1</version>
    </dependency>
    <!-- eureka -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- gateway -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- 监控依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  • 配置路由

    spring:
      application:
        name: shop-gateway-server
      #配置gateway的路由
      cloud:
        gateway:
          routes:
            #路由ID\URI\断言
            - id: product-api
              #使用注册中心;格式需满足 lb:// 注册中心获取微服务的名称
              uri: lb://shop-product-server 
              predicates:
                - Path=/product-service/**
              filters: #配置过滤器,实现路径重写转发
                - RewritePath=/product-service/(?<segment>.*), /$\{segment} 
    #eureka注册中心配置
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:10000/eureka/
    
  • 编写Sentinel限流配置类

    @Configuration
    public class MySentinelConfig {
    
        private final List<ViewResolver> viewResolvers;
        private final ServerCodecConfigurer serverCodecConfigurer;
    
        public MySentinelConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
            this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
            this.serverCodecConfigurer = serverCodecConfigurer;
        }
    
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
            return new SentinelGatewayBlockExceptionHandler(viewResolvers,serverCodecConfigurer);
        }
    
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public GlobalFilter sentinelGatewayFilter(){
            return new SentinelGatewayFilter();
        }
    
        @PostConstruct
        private void initGatewayRules(){
            Set<GatewayFlowRule> rules = new HashSet<>();
            //服务ID限流
    //        rules.add(new GatewayFlowRule("product-api") //资源名称,这里为路由router的ID
    //                //路由模式
    //                .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)
    //                .setCount(1) //QPS即每秒钟允许的调用次数
    //                .setIntervalSec(1)); //每隔多少时间统计一次汇总数据
            //配置分组限流
            rules.add(new GatewayFlowRule("product-api") //分组名称
                    .setCount(1) //QPS即每秒钟允许的调用次数
                    .setIntervalSec(1)); //每隔多少时间统计一次汇总数据
            rules.add(new GatewayFlowRule("order-api") //分组名称
                    .setCount(1) //QPS即每秒钟允许的调用次数
                    .setIntervalSec(1)); //每隔多少时间统计一次汇总数据
            GatewayRuleManager.loadRules(rules);
        }
    
        //自定义分组限流,基于API
        @PostConstruct
        private void initCustomizedApis(){
            Set<ApiDefinition> apiDefinitions = new HashSet<>();
            ApiDefinition api1 = new ApiDefinition("product-api") //分组名称
                    .setPredicateItems(new HashSet<ApiPredicateItem>(){{
                        add(new ApiPathPredicateItem()
                                .setPattern("/product-service/product/**")); //配置匹配的连接API(模糊匹配)
                    }});
            ApiDefinition api2 = new ApiDefinition("order-api")
                    .setPredicateItems(new HashSet<ApiPredicateItem>(){{
                        add(new ApiPathPredicateItem()
                                .setPattern("/product-service/order")); //配置匹配的连接API(完全匹配)
                    }});
            apiDefinitions.add(api1);
            apiDefinitions.add(api2);
            GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
        }
    
        @PostConstruct
        public void initBlockHandlers(){
            //声明自定义异常信息
            BlockRequestHandler handler = new BlockRequestHandler() {
                @Override
                public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                    Map<String,String> result = new HashMap<>();
                    result.put("code","403");
                    result.put("message","对不起,请稍后再试");
                    return ServerResponse.status(HttpStatus.OK)
                            .contentType(MediaType.APPLICATION_JSON_UTF8)
                            .body(BodyInserters.fromObject(result));
                }
            };
            GatewayCallbackManager.setBlockHandler(handler); //设置自定义异常信息
        }
    }
    

3.4 Sentinel流控规则实现

流量控制(Flow Control)其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

流量控制主要有两种方式

  • 并发线程数:并发线程数限流保护业务线程数不被耗尽
  • QPS:当QPS超过阈值,采取措施进行流量控制

一条限流规则主要由下面几个因素组成,通过组合实现不同的限流效果:

  • resource:资源名,即限流规则的作用对象
  • count:限流阈值
  • grade:限流阈值类型(QPS或并发线程数)
  • limitApp:流控针对的调用来源,若为default则不区分调用来源
  • strategy:调用关系限流策略
  • controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)

同一资源可以同时配置多个限流规则,按顺序执行

3.5 Sentinel熔断降级规则实现

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

重要属性

属性说明默认值
resource资源名
count阈值
grade熔断策略,支持秒级RT、秒级异常比例、分钟级异常数秒级RT
timeWindow降级的时间,单位S
rtSlowRequestAmountRT模式下1秒内连续多少个请求的平均RT超出阈值方可触发熔断5
minRequestAmount异常熔断的触发最小请求数,请求数小于该值时即使异常比例超出阈值也不会熔断5

同一资源可以同时有多个降级规则。

熔断策略详解:

  • 平均响应时间(DEGRADE_GRADE_RT):当1秒内连续N个请求的平均响应时间均超出阈值,那么在接下来的熔断时间窗口内,对这个方法的调用都会直接熔断。
  • 异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量>=N,并且每秒异常总数占通过量的比例超出阈值之后,资源降级,之后的时间内对这个资源的访问都会自动返回,异常比例值的范围是[0.01.0],代表0%100%。
  • 异常数(DEGRADE_GRADE_EXCEPTION_COUNT):当资源近1分钟的异常数超出阈值之后会进行熔断,注由于统计时间是分钟级别的,若timeWindow小于60s,则结束熔断状态后仍可能再进入熔断状态。

本地代码实现熔断降级配置:

  • 引入依赖

    <!-- Spring Cloud整合Sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>
    
  • 修改配置文件

    spring:
      application:
        name: SentinelDemo
      cloud:
        sentinel:
          transport:
            dashboard: localhost:8088
    
  • 配置熔断降级资源

    @SentinelResource(value = "testDegrade",blockHandler = "exHandler")
    @RequestMapping("/testDegrade")
    public String hello(){
        return "hello,Sentinel";
    }
    
    public String exHandler(BlockException e){
        System.out.println(e.getMessage());
        return "系统繁忙!";
    }
    
  • 配置熔断降级规则

    //定义熔断降级规则
    @PostConstruct
    public void initDegradeRule(){
        //创建存放熔断降级规则的集合
        List<DegradeRule> rules = new ArrayList<>();
        //创建熔断降级规则
        DegradeRule rule = new DegradeRule();
        //定义资源名
        rule.setResource("testDegrade");
        //阈值 平均响应时间
        rule.setCount(0.01);
        //定义规则类型
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        //定义降级时间,秒级
        rule.setTimeWindow(2);
        //加入集合
        rules.add(rule);
        //加载熔断降级规则
        DegradeRuleManager.loadRules(rules);
    }
    

我们也可以通过本地Sentinel控制台实现熔断降级配置。

在这里插入图片描述

3.6 Sentinel系统自适应保护实现

Sentinel系统自适应限流从整体维度对应用入口流量进行控制,结合应用的Load、CPU使用率、总体平均RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能保持最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效,入口流量指的是进入应用的流量、例如web服务接收的请求等

系统规则支持的格式:

  • Load自适应(仅支持Liunx服务器):系统的load作为启发指标,进行自适应系统保护,当系统load超过设定的启发值且系统当前的并发线程数超过估算的系统容量时才会触发系统保护,系统容量由系统的maxQPS *minRT,设定参考值一般是CPU核心数的2.5倍。
  • CPU usage:当系统CPU使用率超过阈值即触发系统保护(取值范围0.0~1.0)
  • 平均RT:当单台机器上所有人入口流量的平均RT达到阈值即触发系统保护,单位毫秒
  • 并发线程数:当单台机器上所有人入口流量的并发线程数达到阈值即触发系统保护
  • 入口QPS:当单台机器上所有人入口流量的QPS达到阈值即触发系统保护

重要的属性:

属性说明默认值
highestSystemLoadload1触发值,用于触发自适应控制阶段-1(不生效)
avgRt所有入口的平均响应时间-1(不生效)
maxThread入口流量的最大并发数-1(不生效)
qps所有入口资源的QPS-1(不生效)
highestCpuUsage当前系统的CPU使用率(0.0~1.0)-1(不生效)

本地代码实现

@SentinelResource(entryType = EntryType.IN)
@RequestMapping("/testSys")
public String hello(){
    return "hello,Sentinel";
}

//定义系统自适应规则
@PostConstruct
public void initSysRule(){
    //创建存放系统自适应规则的集合
    List<SystemRule> rules = new ArrayList<>();
    //创建系统自适应规则
    SystemRule rule = new SystemRule();
    //定义资源名
    rule.setQps(2);
    //加入集合
    rules.add(rule);
    //加载熔断降级规则
    SystemRuleManager.loadRules(rules);
}

本地控制台实现

在这里插入图片描述

3.7 Sentinel授权控制实现

使用Sentinel的来源访问控制(黑名单白名单控制)判断来源请求是否允许放行,若配置白名单则只有请求来源位于白名单时才可以通过,配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

重要的属性:

  • resource:资源名,即限流规则的作用对象
  • limitApp:请求来源,对应的黑名单/白名单,多个使用‘,’分隔
  • strategy:限制模式,AUTHORITY_WHITE为白名单模式,AUTHORITY_BLACK为黑名单模式,默认为白名单模式。

本地代码实现:

@SentinelResource(value = "TestWB",blockHandler = "exHandler")
@RequestMapping("/testWB")
public String hello()  {
    return "hello,Sentinel";
}

public String exHandler(BlockException e){
    System.out.println(e.getMessage());
    return "系统繁忙!";
}

//设置请求来源解析
@PostConstruct
public void init(){
    WebCallbackManager.setRequestOriginParser(ServletRequest::getRemoteAddr);
}

//定义授权规则
@PostConstruct
public void initWhiteRule(){
    //创建存放授权规则的集合
    List<AuthorityRule> rules = new ArrayList<>();
    //创建授权规则
    AuthorityRule rule = new AuthorityRule();
    //定义资源名
    rule.setResource("TestWB");
    //定义白名单
    rule.setLimitApp("192.168.222.1,127.0.0.1");
    //定义授权模式
    rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
    //加入集合
    rules.add(rule);
    //加载授权规则
    AuthorityRuleManager.loadRules(rules);
}

本地控制台实现:

在这里插入图片描述

3.8 Sentinel动态规则扩展实现

规则管理中心:

  • Nacos
  • Zookeeper
  • Apollo

规则管理中心的实现方式:

  • 拉取式:客户端主动向某个规则管理中心定期轮巡拉取规则,这个规则管理中心可以是文件,甚至是VCS等,这样做的方式是简单,缺点是无法实时获取变更;实现拉取模式的数据源最简单的方式是继承AutoRefreshDataSource抽象类,然后实现readSource方法,在该方法里从指定数据源读取字符串格式的配置数据。
  • 推送式:规则管理中心同一推送,客户端通过注册监听器的方式时刻监听变化,比如使用ZookeeperApollo等作为规则管理中心,这种方式有更好的实时性和一致性保证,实现那推送模式的数据源最简单的方式是继承AbstractDataSource抽象类,在其构造方法中添加监听器,并实现readSource从指定数据源读取字符串格式的配置数据。

使用Zookeeper实现推送式规则配置:

  • 创建项目sentinel_zookeeper

  • 引入依赖

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-zookeeper</artifactId>
        <version>1.8.2</version>
    </dependency>
    
  • 配置文件

    spring:
      application:
        name: SentinelDemo
      cloud:
        sentinel:
          transport:
            dashboard: localhost:8088
    
  • 编写配置类

    @Configuration
    public class ZookeeperSentinelConfig {
    
        //连接zookeeper获取规则
        @PostConstruct
        void loadRules(){
            final String ZOOKEEPER_URL = "127.0.0.1:2181";
            final String ZOOKEEPER_FILE_PATH = "/sentinel/zookeeper";
    
            ReadableDataSource<String, List<FlowRule>> readableDataSource 
                = new ZookeeperDataSource<>(
                    ZOOKEEPER_URL, //zookeeper连接地址
                    ZOOKEEPER_FILE_PATH,  //zookeeper文件路径
                    source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>(){}) //存放的数据类型
            );
            //注册数据资源到FlowRuleManager
            FlowRuleManager.register2Property(readableDataSource.getProperty());
        }
    }
    
  • 编写资源流控配置

    @SentinelResource(value = "TestZK", blockHandler = "exHandler")
    @RequestMapping("/testZK")
    public String hello() {
        return "hello,Sentinel";
    }
    
    public String exHandler(BlockException e) {
        System.out.println(e.getMessage());
        return "系统繁忙!";
    }
    

原理

Sentinel 功能和设计理念

流量控制

什么是流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:

img

流量控制设计理念

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

熔断降级

什么是熔断降级

除了流量控制以外,及时对调用链路中的不稳定因素进行熔断也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,可能会导致请求发生堆积,进而导致级联错误。

img

Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。

熔断降级设计理念

在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。

Hystrix 通过 线程池隔离 的方式,来对依赖(在 Sentinel 的概念中对应 资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配,并且对于一些使用了 ThreadLocal 的场景来说会有问题(如 Spring 事务)。

Sentinel 对这个问题采取了两种手段:

  • 通过并发线程数进行限制

和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。

  • 针对慢调用和异常对资源进行降级

除了对并发线程数进行控制以外,Sentinel 还可以根据响应时间和异常等不稳定因素来快速对不稳定的调用进行熔断。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新渐进式地恢复。

系统自适应保护

Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

Sentinel 是如何工作的

Sentinel 的主要工作机制如下:

  • 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
  • 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
  • Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序小达人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值