Spring Cloud Alibaba | Sentinel: 服務限流基礎篇

weixin_30652271發表於2019-07-19

Spring Cloud Alibaba | Sentinel: 服務限流基礎篇

Springboot: 2.1.6.RELEASE

SpringCloud: Greenwich.SR1

如無特殊說明,本系列文章全採用以上版本

上一篇《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 的方式,都可能會出現大批量的訊息突刺。如果此時要處理所有訊息,很可能會導致系統負載過高,影響穩定性。但其實可能後面幾秒之內都沒有訊息投遞,若直接把多餘的訊息丟掉則沒有充分利用系統處理訊息的能力。我們希望可以把訊息突刺均攤到一段時間內,讓系統負載保持在訊息處理水位之下的同時儘可能地處理更多訊息,從而起到“削峰填谷”的效果:

Spring Cloud Alibaba | Sentinel: 服務限流基礎篇

上圖中紅色的部分代表超出訊息處理能力的部分。我們可以看到訊息突刺往往都是瞬時的、不規律的,其後一段時間系統往往都會有空閒資源。我們希望把紅色的那部分訊息平攤到後面空閒時去處理,這樣既可以保證系統負載處在一個穩定的水位,又可以儘可能地處理更多訊息。Sentinel 專門為這種場景提供了勻速器的特性,可以把突然到來的大量請求以勻速的形式均攤,以固定的間隔時間讓請求通過,以穩定的速度逐步處理這些請求,起到“削峰填谷”的效果,從而避免流量突刺造成系統負載過高。同時堆積的請求將會排隊,逐步進行處理;當請求排隊預計超過最大超時時長的時候則直接拒絕,而不是拒絕全部請求。

比如在 RocketMQ 的場景下配置了勻速模式下請求 QPS 為 5,則會每 200 ms 處理一條訊息,多餘的處理任務將排隊;同時設定了超時時間為 5 s,預計排隊時長超過 5 s 的處理任務將會直接被拒絕。示意圖如下圖所示:

Spring Cloud Alibaba | Sentinel: 服務限流基礎篇

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 為黑名單模式,預設為白名單模式

相關文章