天天看點

阿裡巴巴開源限流元件Sentinel初探之內建Gateway

1 Sentinel首頁

​​github.com/alibaba/Sen…​​

1.1 Sentinel介紹

阿裡巴巴開源限流元件Sentinel初探之內建Gateway

随着微服務的流行,服務和服務之間的穩定性變得越來越重要。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 作為一個調配器,可以根據需要把随機的請求調整成合适的形狀,如下圖所示:

阿裡巴巴開源限流元件Sentinel初探之內建Gateway

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

  • 資源的調用關系,例如資源的調用鍊路,資源和資源之間的關系;
  • 運作名額,例如 QPS、線程池、系統負載等;
  • 控制的效果,例如直接限流、冷啟動、排隊等。

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

1.2.2 熔斷降級

1)什麼是熔斷降級

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

阿裡巴巴開源限流元件Sentinel初探之內建Gateway

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 進行限流。

阿裡巴巴開源限流元件Sentinel初探之內建Gateway

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

阿裡巴巴開源限流元件Sentinel初探之內建Gateway

我們如果想要讓微服務網關內建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-gateway​

​​,​

​hailtaxi-drvier​

​​,​

​hailtaxi-order​

​測試: