2023-02-23  阅读(1)
原文作者:极客挖掘机 原文地址:https://www.cnblogs.com/babycomeon/category/1493460.html

Spring Cloud Alibaba | Sentinel: 服务限流基础篇

Springboot: 2.1.6.RELEASE

SpringCloud: Greenwich.SR1

如无特殊说明,本系列文章全采用以上版本

目录

  • Spring Cloud Alibaba | Sentinel: 服务限流基础篇

      1. 简介
      1. 定义资源
      • 2.1 主流框架的默认适配

        • 2.1.1 Web Servlet
        • 2.1.2 Dubbo
        • 2.1.3 Spring Cloud
        • 2.1.4 Spring WebFlux
        • 2.1.5 gRPC
        • 2.1.6 Reactive
        • 2.1.7 API Gateway
        • 2.1.8 Apache RocketMQ
      • 2.2 抛出异常的方式定义资源

      • 2.3 返回布尔值方式定义资源

      • 2.4 注解方式定义资源

      • 2.5 异步调用支持

      1. 规则的种类
      • 3.1 流量控制规则 (FlowRule)
      • 3.2 熔断降级规则 (DegradeRule)
      • 3.3 系统保护规则 (SystemRule)
      • 3.4 访问控制规则 (AuthorityRule)

上一篇《Spring Cloud Alibaba | Sentinel: 分布式系统的流量防卫兵初探》我们介绍了Sentinel是什么,并且做了一个简单的Demo来验证服务限流。

这一篇,我们主要讲什么是服务限流,包括Sentinel支持的限流方式。

在讲服务限流之前,我们先了解什么是资源。

1. 简介

资源:可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:

  1. 定义资源
  2. 定义规则
  3. 检验规则是否生效

先把可能需要保护的资源定义好,之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。

2. 定义资源

2.1 主流框架的默认适配

为了减少开发的复杂程度,Sentinel对大部分的主流框架,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了适配。

2.1.1 Web Servlet

Sentinel 提供与 Servlet 的整合,可以对 Web 请求进行流量控制。使用时需引入以下模块(以 Maven 为例):

    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-web-servlet</artifactId>
        <version>x.y.z</version>
    </dependency>

仅需要在 Web 容器中的 web.xml 配置文件中进行如下配置即可开启 Sentinel 支持:

    <filter>
    	<filter-name>SentinelCommonFilter</filter-name>
    	<filter-class>com.alibaba.csp.sentinel.adapter.servlet.CommonFilter</filter-class>
    </filter>
    
    <filter-mapping>
    	<filter-name>SentinelCommonFilter</filter-name>
    	<url-pattern>/*</url-pattern>
    </filter-mapping>

若是 Spring 应用可以通过 Spring 进行配置,例如:

    @Configuration
    public class FilterConfig {
    
      @Bean
      public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        registration.setName("sentinelFilter");
        registration.setOrder(1);
    
        return registration;
      }
    }

默认情况下,当请求被限流时会返回默认的提示页面。您也可以通过 WebServletConfig.setBlockPage(blockPage) 方法设定自定义的跳转 URL,当请求被限流时会自动跳转至设定好的 URL。同样也可以实现 UrlBlockHandler 接口并编写定制化的限流处理逻辑,然后将其注册至 WebCallbackManager 中。

注意: Sentinel Web Filter 会将每个到来的不同的 URL 都作为不同的资源处理,因此对于 REST 风格的 API,需要自行实现 UrlCleaner 接口清洗一下资源(比如将满足 /foo/:id 的 URL 都归到 /foo/* 资源下),然后将其注册至 WebCallbackManager 中。否则会导致资源数量过多,超出资源数量阈值(目前是 6000)时多出的资源的规则将 不会生效。

2.1.2 Dubbo

Sentinel 提供 Dubbo 的相关适配 Sentinel Dubbo Adapter,主要包括针对 Service Provider 和 Service Consumer 实现的 Filter。相关模块:

  • sentinel-apache-dubbo-adapter(兼容 Apache Dubbo 2.7.x 及以上版本,自 Sentinel 1.5.1 开始支持)
  • sentinel-dubbo-adapter(兼容 Dubbo 2.6.x 版本)

对于 Apache Dubbo 2.7.x 及以上版本,使用时需引入以下模块(以 Maven 为例):

    <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-apache-dubbo-adapter</artifactId>
      <version>x.y.z</version>
    </dependency>

对于 Dubbo 2.6.x 及以下版本,使用时需引入以下模块(以 Maven 为例):

    <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-dubbo-adapter</artifactId>
      <version>x.y.z</version>
    </dependency>

引入此依赖后,Dubbo 的服务接口和方法(包括调用端和服务端)就会成为 Sentinel 中的资源,在配置了规则后就可以自动享受到 Sentinel 的防护能力。

若不希望开启 Sentinel Dubbo Adapter 中的某个 Filter,可以手动关闭对应的 Filter,比如:

    <!-- 关闭 Sentinel 对应的 Service Consumer Filter -->
    <dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>

限流粒度可以是服务接口和服务方法两种粒度:

  • 服务接口:resourceName 为 接口全限定名,如 com.alibaba.csp.sentinel.demo.dubbo.FooService
  • 服务方法:resourceName 为 接口全限定名:方法签名,如 com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)

2.1.3 Spring Cloud

引入依赖:

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

下面这个例子就是一个最简单的使用 Sentinel 的例子:

    @SpringBootApplication
    public class Application {
      public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class, args);
      }
    }
    
    @RestController
    public class TestController {
      @GetMapping(value = "/hello")
      @SentinelResource("hello")
      public String hello() {
        return "Hello Sentinel";
      }
    }

@SentinelResource 注解用来标识资源是否被限流、降级。上述例子上该注解的属性 'hello' 表示资源名。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)

  • entryTypeentry 类型,可选项(默认为 EntryType.OUT

  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockExceptionblockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • fallbackfallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallbackdefaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

特别地,若 blockHandlerfallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出。

示例:

    public class TestService {
    
      // 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.
      @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
      public void test() {
        System.out.println("Test");
      }
    
      // 原函数
      @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
      public String hello(long s) {
        return String.format("Hello at %d", s);
      }
      
      // Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
      public String helloFallback(long s) {
        return String.format("Halooooo %d", s);
      }
    
      // Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
      public String exceptionHandler(long s, BlockException ex) {
        // Do some log here.
        ex.printStackTrace();
        return "Oops, error occurred at " + s;
      }
    }

从 1.4.0 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex) 来记录业务异常。Sentinel 1.4.0 以前的版本需要自行调用 Tracer.trace(ex) 来记录业务异常。

AspectJ

如果应用直接使用了 AspectJ,那么需要在 aop.xml 文件中引入对应的 Aspect:

    <aspects>
      <aspect name="com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect"/>
    </aspects>

Spring AOP

如果应用使用了 Spring AOP,需要通过配置的方式将 SentinelResourceAspect 注册为一个 Spring Bean:

    @Configuration
    public class SentinelAspectConfiguration {
      @Bean
      public SentinelResourceAspect sentinelResourceAspect() {
          return new SentinelResourceAspect();
      }
    }

2.1.4 Spring WebFlux

注:从 1.5.0 版本开始支持,需要 Java 8 及以上版本。

Sentinel 提供与 Spring WebFlux 的整合模块,从而 Reactive Web 应用也可以利用 Sentinel 的流控降级来保障稳定性。该整合模块基于 Sentinel Reactor Adapter 实现。

使用时需引入以下模块(以 Maven 为例):

    <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-spring-webflux-adapter</artifactId>
      <version>x.y.z</version>
    </dependency>

使用时只需注入对应的 SentinelWebFluxFilter 实例以及 SentinelBlockExceptionHandler 实例即可。比如:

    @Configuration
    public class WebFluxConfig {
    
      private final List<ViewResolver> viewResolvers;
      private final ServerCodecConfigurer serverCodecConfigurer;
    
      public WebFluxConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                            ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
      }
    
      @Bean
      @Order(-1)
      public SentinelBlockExceptionHandler sentinelBlockExceptionHandler() {
        // Register the block exception handler for Spring WebFlux.
        return new SentinelBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
      }
    
      @Bean
      @Order(-1)
      public SentinelWebFluxFilter sentinelWebFluxFilter() {
        // Register the Sentinel WebFlux filter.
        return new SentinelWebFluxFilter();
      }
    }

您可以在 WebFluxCallbackManager 注册回调进行定制:

  • setBlockHandler:注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为 BlockRequestHandler。默认实现为 DefaultBlockRequestHandler,当被限流时会返回类似于下面的错误信息:Blocked by Sentinel: FlowException
  • setUrlCleaner:注册函数用于 Web 资源名的归一化。函数类型为 (ServerWebExchange, String) → String,对应含义为 (webExchange, originalUrl) → finalUrl
  • setRequestOriginParser:注册函数用于从请求中解析请求来源。函数类型为 ServerWebExchange → String

2.1.5 gRPC

Sentinel 提供与 gRPC Java 的整合,以 gRPC ServerInterceptor 和 ClientInterceptor 的形式保护 gRPC 服务资源。

使用时需引入以下模块(以 Maven 为例):

    <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-grpc-adapter</artifactId>
      <version>x.y.z</version>
    </dependency>

在使用 Sentinel gRPC Adapter 时,只需要将对应的 Interceptor 注册至对应的客户端或服务端中。其中客户端的示例如下:

    public class ServiceClient {
      private final ManagedChannel channel;
      ServiceClient(String host, int port) {
        this.channel = ManagedChannelBuilder.forAddress(host, port)
            .intercept(new SentinelGrpcClientInterceptor()) // 在此处注册拦截器
            .build();
        // 在此处初始化客户端 stub 类
      }
    }

服务端的示例如下:

    import io.grpc.Server;
    
    Server server = ServerBuilder.forPort(port)
         .addService(new MyServiceImpl()) // 添加自己的服务实现
         .intercept(new SentinelGrpcServerInterceptor()) // 在此处注册拦截器
         .build();

注意:由于 gRPC 拦截器中 ClientCall/ServerCall 以回调的形式进行请求响应信息的获取,每次 gRPC 服务调用计算出的 RT 可能会不准确。Sentinel gRPC Adapter 目前只支持 unary call。

2.1.6 Reactive

注:从 1.5.0 版本开始支持,需要 Java 8 及以上版本。

Sentinel 提供 Reactor 的适配,可以方便地在 reactive 应用中接入 Sentinel。

使用时需引入以下模块(以 Maven 为例):

    <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-reactor-adapter</artifactId>
      <version>x.y.z</version>
    </dependency>

Sentinel Reactor Adapter 分别针对 Mono 和 Flux 实现了对应的 Sentinel Operator,从而在各种事件触发时汇入 Sentinel 的相关逻辑。同时 Sentinel 在上层提供了 SentinelReactorTransformer 用于在组装期装入对应的 operator,用户使用时只需要通过 transform 操作符来进行变换即可。

接入示例:

    someService.doSomething() // return type: Mono<T> or Flux<T>
       .transform(new SentinelReactorTransformer<>(resourceName)) // 在此处进行变换
       .subscribe();

2.1.7 API Gateway

Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。

Spring Cloud Gateway

从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:

  • route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
  • 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组

使用时需引入以下模块(以 Maven 为例):

    <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
      <version>x.y.z</version>
    </dependency>

使用时只需注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可。比如:

    @Configuration
    public class GatewayConfiguration {
    
        private final List<ViewResolver> viewResolvers;
        private final ServerCodecConfigurer serverCodecConfigurer;
    
        public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                    ServerCodecConfigurer serverCodecConfigurer) {
            this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
            this.serverCodecConfigurer = serverCodecConfigurer;
        }
    
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
            // Register the block exception handler for Spring Cloud Gateway.
            return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
        }
    
        @Bean
        @Order(-1)
        public GlobalFilter sentinelGatewayFilter() {
            return new SentinelGatewayFilter();
        }
    }

因为Sentinel暂时只支持了Zuul1.x,具体用发这里暂不介绍。

2.1.8 Apache RocketMQ

在 Apache RocketMQ 中,当消费者去消费消息的时候,无论是通过 pull 的方式还是 push 的方式,都可能会出现大批量的消息突刺。如果此时要处理所有消息,很可能会导致系统负载过高,影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力。我们希望可以把消息突刺均摊到一段时间内,让系统负载保持在消息处理水位之下的同时尽可能地处理更多消息,从而起到“削峰填谷”的效果:

202302232310052101.png

上图中红色的部分代表超出消息处理能力的部分。我们可以看到消息突刺往往都是瞬时的、不规律的,其后一段时间系统往往都会有空闲资源。我们希望把红色的那部分消息平摊到后面空闲时去处理,这样既可以保证系统负载处在一个稳定的水位,又可以尽可能地处理更多消息。Sentinel 专门为这种场景提供了匀速器的特性,可以把突然到来的大量请求以匀速的形式均摊,以固定的间隔时间让请求通过,以稳定的速度逐步处理这些请求,起到“削峰填谷”的效果,从而避免流量突刺造成系统负载过高。同时堆积的请求将会排队,逐步进行处理;当请求排队预计超过最大超时时长的时候则直接拒绝,而不是拒绝全部请求。

比如在 RocketMQ 的场景下配置了匀速模式下请求 QPS 为 5,则会每 200 ms 处理一条消息,多余的处理任务将排队;同时设置了超时时间为 5 s,预计排队时长超过 5 s 的处理任务将会直接被拒绝。示意图如下图所示:

202302232310060612.png

RocketMQ 用户可以根据不同的 group 和不同的 topic 分别设置限流规则,限流控制模式设置为匀速器模式(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER),比如:

    
    private void initFlowControlRule() {
        FlowRule rule = new FlowRule();
        rule.setResource(KEY); // 对应的 key 为 `groupName:topicName`
        rule.setCount(5);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setLimitApp("default");
    
        // 匀速器模式下,设置了 QPS 为 5,则请求每 200 ms 允许通过 1 个
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
        // 如果更多的请求到达,这些请求会被置于虚拟的等待队列中。等待队列有一个 max timeout,如果请求预计的等待时间超过这个时间会直接被 block
        // 在这里,timeout 为 5s
        rule.setMaxQueueingTimeMs(5 * 1000);
        FlowRuleManager.loadRules(Collections.singletonList(rule));
    }

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

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

    // 1.5.0 版本开始可以利用 try-with-resources 特性
    // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
    try (Entry entry = SphU.entry("resourceName")) {
      // 被保护的业务逻辑
      // do something here...
    } catch (BlockException ex) {
      // 资源访问阻止,被限流或被降级
      // 在此处进行相应的处理操作
    }

特别地 ,若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外通过 Tracer.trace(ex) 来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)。

1.5.0 之前的版本的示例:

    Entry entry = null;
    // 务必保证finally会被执行
    try {
      // 资源名可使用任意有业务语义的字符串
      entry = SphU.entry("自定义资源名");
      // 被保护的业务逻辑
      // do something...
    } catch (BlockException e1) {
      // 资源访问阻止,被限流或被降级
      // 进行相应的处理操作
    } finally {
      if (entry != null) {
        entry.exit();
      }
    }

注意 : SphU.entry(xxx) 需要与 entry.exit() 方法成对出现,匹配调用,否则会导致调用链记录异常,抛出 ErrorEntryFreeException 异常。

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

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

    // 资源名可使用任意有业务语义的字符串
    if (SphO.entry("自定义资源名")) {
      // 务必保证finally会被执行
      try {
        /**
        * 被保护的业务逻辑
        */
      } finally {
        SphO.exit();
      }
    } else {
      // 资源访问阻止,被限流或被降级
      // 进行相应的处理操作
    }

2.4 注解方式定义资源

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

    // 原本的业务方法.
    @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");
    }

注意 blockHandler 函数会在原方法被限流/降级/系统保护的时候调用,而 fallback 函数会针对所有类型的异常。请注意 blockHandler 和 fallback 函数的形式要求。

2.5 异步调用支持

Sentinel 支持异步调用链路的统计。在异步调用中,需要通过 SphU.asyncEntry(xxx) 方法定义资源,并通常需要在异步的回调函数中调用 exit 方法。以下是一个简单的示例:

    try {
        AsyncEntry entry = SphU.asyncEntry(resourceName);
    
        // 异步调用.
        doAsync(userId, result -> {
            try {
                // 在此处处理异步调用的结果.
            } finally {
                // 在回调结束后 exit.
                entry.exit();
            }
        });
    } catch (BlockException ex) {
        // Request blocked.
        // Handle the exception (e.g. retry or fallback).
    }

SphU.asyncEntry(xxx) 不会影响当前(调用线程)的 Context,因此以下两个 entry 在调用链上是平级关系(处于同一层),而不是嵌套关系:

    // 调用链类似于:
    // -parent
    // ---asyncResource
    // ---syncResource
    asyncEntry = SphU.asyncEntry(asyncResource);
    entry = SphU.entry(normalResource);

若在异步回调中需要嵌套其它的资源调用(无论是 entry 还是 asyncEntry),只需要借助 Sentinel 提供的上下文切换功能,在对应的地方通过 ContextUtil.runOnContext(context, f) 进行 Context 变换,将对应资源调用处的 Context 切换为生成的异步 Context,即可维持正确的调用链路关系。示例如下:

    public void handleResult(String result) {
        Entry entry = null;
        try {
            entry = SphU.entry("handleResultForAsync");
            // Handle your result here.
        } catch (BlockException ex) {
            // Blocked for the result handler.
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }
    
    public void someAsync() {
        try {
            AsyncEntry entry = SphU.asyncEntry(resourceName);
    
            // Asynchronous invocation.
            doAsync(userId, result -> {
                // 在异步回调中进行上下文变换,通过 AsyncEntry 的 getAsyncContext 方法获取异步 Context
                ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
                    try {
                        // 此处嵌套正常的资源调用.
                        handleResult(result);
                    } finally {
                        entry.exit();
                    }
                });
            });
        } catch (BlockException ex) {
            // Request blocked.
            // Handle the exception (e.g. retry or fallback).
        }
    }

3. 规则的种类

Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略。

Sentinel 支持以下几种规则: 流量控制规则熔断降级规则系统保护规则来源访问控制规则热点参数规则

3.1 流量控制规则 (FlowRule)

流量规则的定义

重要属性:

Field 说明 默认值
resource 资源名,资源名是限流规则的作用对象
count 限流阈值
grade 限流阈值类型,QPS或线程数模式 QPS模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 判断的根据是资源自身,还是根据其它关联资源(refResource),还是根据链路入口 根据资源本身
controlBehavior 流控效果(直接拒绝/排队等待/慢启动模式) 直接拒绝

同一个资源可以同时有多个限流规则。

通过代码定义流量控制规则

理解上面规则的定义之后,我们可以通过调用 FlowRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则,比如:

    private void initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule(resourceName);
        // set limit qps to 20
        rule.setCount(20);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setLimitApp("default");
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

3.2 熔断降级规则 (DegradeRule)

熔断降级规则包含下面几个重要的属性:

Field 说明 默认值
resource 资源名,即限流规则的作用对象
count 阈值
grade 降级模式,根据RT降级还是根据异常比例降级
timeWindow 降级的时间,单位为s

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

理解上面规则的定义之后,我们可以通过调用 DegradeRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。

    private void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        rule.setResource(KEY);
        // set threshold RT, 10 ms
        rule.setCount(10);
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        rule.setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

3.3 系统保护规则 (SystemRule)

规则包含下面几个重要的属性:

Field 说明 默认值
highestSystemLoad 最大的load1,参考值 -1(不生效)
avgRt 所有入口流量的平均响应时间 -1(不生效)
maxThread 入口流量的最大并发数 -1(不生效)
qps 所有入口资源的QPS -1(不生效)

理解上面规则的定义之后,我们可以通过调用 SystemRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。

    private void initSystemRule() {
        List<SystemRule> rules = new ArrayList<>();
        SystemRule rule = new SystemRule();
        rule.setHighestSystemLoad(10);
        rules.add(rule);
        SystemRuleManager.loadRules(rules);
    }

3.4 访问控制规则 (AuthorityRule)

很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

授权规则,即黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • resource:资源名,即限流规则的作用对象
  • limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
  • strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式

这一篇先初步的介绍什么是资源,什么是规则,并且介绍了Sentinel对常见主流框架的适配,下一篇,我们详细来聊聊几种服务限流的方式。

参考:

https://github.com/alibaba/Sentinel


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文