阿里巴巴開源限流元件Sentinel初探之整合Gateway

博學谷狂野架構師發表於2022-06-07

1 Sentinel主頁

https://github.com/alibaba/Sentinel/wiki/主頁

1.1 Sentinel介紹

file

隨著微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 是面向分散式服務架構的流量控制元件,主要以流量為切入點,從限流、流量整形、熔斷降級、系統負載保護、熱點防護等多個維度來幫助開發者保障微服務的穩定性。

1)Sentinel核心元件

1:核心庫(Java 客戶端):不依賴任何框架/庫,能夠執行於 Java 7 及以上的版本的執行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支援。

2:控制檯(Dashboard):控制檯主要負責管理推送規則、監控、叢集限流分配管理、機器發現等。

2)Sentinel vs Hystrix

對比內容 Sentinel Hystrix
隔離策略 訊號量隔離 執行緒池隔離/訊號量隔離
熔斷降級策略 基於響應時間或失敗比率 基於失敗比率
實時指標實現 滑動視窗 滑動視窗(基於 RxJava)
規則配置 支援多種資料來源 支援多種資料來源
擴充套件性 多個擴充套件點 外掛的形式
基於註解的支援 支援 支援
限流 基於 QPS,支援基於呼叫關係的限流 不支援
流量整形 支援慢啟動、勻速器模式 不支援
系統負載保護 支援 不支援
控制檯 開箱即用,可配置規則、檢視秒級監控、機器發現等 不完善
常見框架的適配 Servlet、Spring Cloud、Dubbo、gRPC 等 Servlet、Spring Cloud Netflix

3)Sentinel基本概念

  • 資源
資源是 Sentinel 的關鍵概念。它可以是 Java 應用程式中的任何內容,例如,由應用程式提供的服務,或由應用程式呼叫的其它應用提供的服務,甚至可以是一段程式碼。

只要通過 Sentinel API 定義的程式碼,就是資源,能夠被 Sentinel 保護起來。大部分情況下,可以使用方法簽名,URL,甚至服務名稱作為資源名來標示資源。
  • 規則
圍繞資源的實時狀態設定的規則,可以包括流量控制規則、熔斷降級規則以及系統保護規則。所有規則可以動態實時調整。

1.2 Sentinel核心功能

1.2.1 流量控制

流量控制在網路傳輸中是一個常用的概念,它用於調整網路包的傳送資料。然而,從系統穩定性角度考慮,在處理請求的速度上,也有非常多的講究。任意時間到來的請求往往是隨機不可控的,而系統的處理能力是有限的。我們需要根據系統的處理能力對流量進行控制。Sentinel 作為一個調配器,可以根據需要把隨機的請求調整成合適的形狀,如下圖所示:

file

流量控制有以下幾個角度:

  • 資源的呼叫關係,例如資源的呼叫鏈路,資源和資源之間的關係;
  • 執行指標,例如 QPS、執行緒池、系統負載等;
  • 控制的效果,例如直接限流、冷啟動、排隊等。

Sentinel 的設計理念是讓您自由選擇控制的角度,並進行靈活組合,從而達到想要的效果。

1.2.2 熔斷降級

1)什麼是熔斷降級

除了流量控制以外,及時對呼叫鏈路中的不穩定因素進行熔斷也是 Sentinel 的使命之一。由於呼叫關係的複雜性,如果呼叫鏈路中的某個資源出現了不穩定,可能會導致請求發生堆積,進而導致級聯錯誤。

file

Sentinel 和 Hystrix 的原則是一致的: 當檢測到呼叫鏈路中某個資源出現不穩定的表現,例如請求響應時間長或異常比例升高的時候,則對這個資源的呼叫進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯故障。

2)Sentinel熔斷降級設計

Hystrix 通過 執行緒池隔離 的方式,來對依賴(在 Sentinel 的概念中對應 資源)進行了隔離。這樣做的好處是資源和資源之間做到了最徹底的隔離。缺點是除了增加了執行緒切換的成本(過多的執行緒池導致執行緒數目過多),還需要預先給各個資源做執行緒池大小的分配。

Sentinel熔斷降級設計:

併發執行緒數限制:和資源池隔離的方法不同,Sentinel 通過限制資源併發執行緒的數量,來減少不穩定資源對其它資源的影響。這樣不但沒有執行緒切換的損耗,也不需要您預先分配執行緒池的大小。當某個資源出現不穩定的情況下,例如響應時間變長,對資源的直接影響就是會造成執行緒數的逐步堆積。當執行緒數在特定資源上堆積到一定的數量之後,對該資源的新請求就會被拒絕。堆積的執行緒完成任務後才開始繼續接收請求。

響應時間降級:除了對併發執行緒數進行控制以外,Sentinel 還可以通過響應時間來快速降級不穩定的資源。當依賴的資源出現響應時間過長後,所有對該資源的訪問都會被直接拒絕,直到過了指定的時間視窗之後才重新恢復。

3)系統自適應保護

Sentinel 同時提供系統維度的自適應保護能力。防止雪崩,是系統防護中重要的一環。當系統負載較高的時候,如果還持續讓請求進入,可能會導致系統崩潰,無法響應。在叢集環境下,網路負載均衡會把本應這臺機器承載的流量轉發到其它的機器上去。如果這個時候其它的機器也處在一個邊緣狀態的時候,這個增加的流量就會導致這臺機器也崩潰,最後導致整個叢集不可用。

針對這個情況,Sentinel 提供了對應的保護機制,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力範圍之內處理最多的請求。

Sentinel整合Gateway

我們的專案流量入口是SpringCloud Gateway,因此我們重點講解Sentinel整合Gateway

3.1 Sentinel對閘道器支援

Sentinel 支援對 Spring Cloud Gateway、Zuul 等主流的 API Gateway 進行限流。

file

Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模組,此模組中包含閘道器限流的規則和自定義 API 的實體和管理邏輯

從 1.6.0 版本開始,Sentinel 提供了 Spring Cloud Gateway 的適配模組,可以提供兩種資源維度的限流:

  • route 維度:即在 Spring 配置檔案中配置的路由條目,資源名為對應的 routeId

    spring: 
    gateway:
    #路由配置
    routes:
      #唯一識別符號
          - id: hailtaxi-driver
            uri: lb://hailtaxi-driver
            #路由斷言
            predicates:
              - Path=/driver/**
          #唯一識別符號
          - id: hailtaxi-order
            uri: lb://hailtaxi-order
            #路由斷言
            predicates:
              - Path=/order/**
    

    自動將每個路由標識為資源,

  • 自定義 API 維度:使用者可以利用 Sentinel 提供的 API 來自定義一些 API 分組

    這兩種維度分別對應如下:

  • GatewayFlowRule:閘道器限流規則,針對 API Gateway 的場景定製的限流規則,可以針對不同 route 或自定義的 API 分組進行限流,支援針對請求中的引數、Header、來源 IP 等進行定製化的限流。

  • ApiDefinition:使用者自定義的 API 定義分組,可以看做是一些 URL 匹配的組合。比如我們可以定義一個 API 叫 my_api,請求 path 模式為 /foo/**/baz/** 的都歸到 my_api 這個 API 分組下面。限流的時候可以針對這個自定義的 API 分組維度進行限流。

其中閘道器限流規則 GatewayFlowRule 的欄位解釋如下:

  • resource:資源名稱,可以是閘道器中的 route 名稱或者使用者自定義的 API 分組名稱。

  • resourceMode:規則是針對 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)還是使用者在 Sentinel 中定義的 API 分組(RESOURCE_MODE_CUSTOM_API_NAME),預設是 route。

  • grade:限流指標維度,同限流規則的 grade 欄位。

  • count:限流閾值

  • intervalSec:統計時間視窗,單位是秒,預設是 1 秒。

  • controlBehavior:流量整形的控制效果,同限流規則的 controlBehavior 欄位,目前支援快速失敗和勻速排隊兩種模式,預設是快速失敗。

  • burst:應對突發請求時額外允許的請求數目。

  • maxQueueingTimeoutMs:勻速排隊模式下的最長排隊時間,單位是毫秒,僅在勻速排隊模式下生效。

  • paramItem:引數限流配置。若不提供,則代表不針對引數進行限流,該閘道器規則將會被轉換成普通流控規則;否則會轉換成熱點規則。其中的欄位:

  • parseStrategy:從請求中提取引數的策略,目前支援提取來源 IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意 Header(PARAM_PARSE_STRATEGY_HEADER)和任意 URL 引數(PARAM_PARSE_STRATEGY_URL_PARAM)四種模式。

    • fieldName:若提取策略選擇 Header 模式或 URL 引數模式,則需要指定對應的 header 名稱或 URL 引數名稱。
  • pattern:引數值的匹配模式,只有匹配該模式的請求屬性值會納入統計和流控;若為空則統計該請求屬性的所有值。(1.6.2 版本開始支援)

    • matchStrategy:引數值的匹配策略,目前支援精確匹配(PARAM_MATCH_STRATEGY_EXACT)、子串匹配(PARAM_MATCH_STRATEGY_CONTAINS)和正則匹配(PARAM_MATCH_STRATEGY_REGEX)。(1.6.2 版本開始支援)

使用者可以通過 GatewayRuleManager.loadRules(rules) 手動載入閘道器規則,或通過 GatewayRuleManager.register2Property(property) 註冊動態規則源動態推送(推薦方式)。

3.2 GateWay整合Sentinel

file

我們如果想要讓微服務閘道器整合Sentinel,需要引入依賴包,使用時只需注入對應的 SentinelGatewayFilter 例項以及 SentinelGatewayBlockExceptionHandler 例項即可。

1、首先在hailtaxi-gateway中引入如下依賴:

<!--Sentinel-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
    <version>1.8.0</version>
</dependency>

2、例項引入:建立配置類com.itheima.config.GatewayConfiguration:

package com.itheima.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@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;
    }

    /**
     * 限流的異常處理器
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /***
     * Sentinel路由處理核心過濾器
     * @return
     */
    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }



    @PostConstruct
    public void doInit() {
        // 自定義 api 分組
        initCustomizedApis();
        // 初始化閘道器流控規則
        initGatewayRules();
    }

    private void initCustomizedApis() {
        Set<ApiDefinition> definitions = new HashSet<>();
        ApiDefinition api1 = new ApiDefinition("customer_api")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem().setPattern("/order/**")
                            /**
                             * 匹配策略:
                             * URL_MATCH_STRATEGY_EXACT:url精確匹配
                             * URL_MATCH_STRATEGY_PREFIX:url字首匹配
                             * URL_MATCH_STRATEGY_REGEX:url正則匹配
                             */
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        definitions.add(api1);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();

        rules.add(new GatewayFlowRule("hailtaxi-driver") // 資源名稱,可以是閘道器中的 routeid或者使用者自定義的 API分組名稱
                .setCount(2) // 限流閾值
                .setIntervalSec(10) // 統計時間視窗預設1s
                .setGrade(RuleConstant.FLOW_GRADE_QPS) // 限流模式
                /**
                 * 限流行為:
                 * CONTROL_BEHAVIOR_RATE_LIMITER 勻速排隊
                 * CONTROL_BEHAVIOR_DEFAULT 快速失敗(預設)
                 * CONTROL_BEHAVIOR_WARM_UP:
                 * CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
                 */
                .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
                //勻速排隊模式下的最長排隊時間,單位是毫秒,僅在勻速排隊模式下生效
                .setMaxQueueingTimeoutMs(1000)
                /**
                 * 熱點引數限流配置
                 * 若不設定,該閘道器規則將會被轉換成普通流控規則;否則會轉換成熱點規則
                 */
                .setParamItem(new GatewayParamFlowItem()
                        /**
                         * 從請求中提取引數的策略:
                         * PARAM_PARSE_STRATEGY_CLIENT_IP
                         * PARAM_PARSE_STRATEGY_HOST
                         * PARAM_PARSE_STRATEGY_HEADER
                         * PARAM_PARSE_STRATEGY_URL_PARAM
                         */
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
                        /**
                         * 若提取策略選擇 Header 模式或 URL 引數模式,
                         * 則需要指定對應的 header 名稱或 URL 引數名稱。
                         */
                        .setFieldName("token")
                        /**
                         * 引數的匹配策略:
                         * PARAM_MATCH_STRATEGY_EXACT
                         * PARAM_MATCH_STRATEGY_PREFIX
                         * PARAM_MATCH_STRATEGY_REGEX
                         * PARAM_MATCH_STRATEGY_CONTAINS
                         */
                        .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT)
                        //引數值的匹配模式,只有匹配該模式的請求屬性值會納入統計和流控
                        .setPattern("123456") // token=123456 10s內qps達到2次會被限流
                )
        );

        rules.add(new GatewayFlowRule("customer_api")
                /**
                 * 規則是針對 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)
                 * 還是使用者在 Sentinel 中定義的 API 分組(RESOURCE_MODE_CUSTOM_API_NAME),預設是 route。
                 */
                .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
                .setCount(2)
                .setIntervalSec(1)
                .setGrade(RuleConstant.FLOW_GRADE_QPS)
        );
        GatewayRuleManager.loadRules(rules);
    }


}

此時整合就完成了。

3、啟動hailtaxi-gatewayhailtaxi-drvierhailtaxi-order測試:

使用postman測試,

請求:http://localhost:8001/driver/info/1 10秒內請求超過2次會被限流

請求:localhost:8001/order 1秒內qps達到2次會被限流

本文由傳智教育博學谷 - 狂野架構師教研團隊釋出
轉載請註明出處!

相關文章