網(wǎng)關(guān)限流實踐

介紹

概覽圖

image.png
image.png

Sentinel API Gateway Adapter Common

**Sentinel 1.6.0 **引入了 Sentinel API Gateway Adapter Common 模塊,此模塊中包含網(wǎng)關(guān)限流的規(guī)則和自定義 API 的實體和管理邏輯:

  • GatewayFlowRule:網(wǎng)關(guān)限流規(guī)則,針對 API Gateway 的場景定制的限流規(guī)則,可以針對不同 route 或自定義的 API 分組進行限流,支持針對請求中的參數(shù)、Header、來源 IP 等進行定制化的限流。
  • ApiDefinition:用戶自定義的 API 定義分組,可以看做是一些 URL 匹配的組合。比如我們可以定義一個 API 叫 my_api,請求 path 模式為 /foo/**/baz/** 的都?xì)w到 my_api 這個 API 分組下面。限流的時候可以針對這個自定義的 API 分組維度進行限流。

GatewayFlowRule

其中網(wǎng)關(guān)限流規(guī)則 GatewayFlowRule 的字段解釋如下:

  • resource:資源名稱,可以是網(wǎng)關(guān)中的 route 名稱或者用戶自定義的 API 分組名稱。
  • resourceMode:規(guī)則是針對 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)還是用戶在 Sentinel 中定義的 API 分組(RESOURCE_MODE_CUSTOM_API_NAME),默認(rèn)是 route。
  • grade:限流指標(biāo)維度,同限流規(guī)則的 grade 字段。
  • count:限流閾值
  • intervalSec:統(tǒng)計時間窗口,單位是秒,默認(rèn)是 1 秒。
  • controlBehavior:流量整形的控制效果,同限流規(guī)則的 controlBehavior 字段,目前支持快速失敗和勻速排隊兩種模式,默認(rèn)是快速失敗。
  • burst:應(yīng)對突發(fā)請求時額外允許的請求數(shù)目。
  • maxQueueingTimeoutMs:勻速排隊模式下的最長排隊時間,單位是毫秒,僅在勻速排隊模式下生效。
  • paramItem:參數(shù)限流配置。若不提供,則代表不針對參數(shù)進行限流,該網(wǎng)關(guān)規(guī)則將會被轉(zhuǎn)換成普通流控規(guī)則;否則會轉(zhuǎn)換成熱點規(guī)則。其中的字段:
    • parseStrategy:從請求中提取參數(shù)的策略,目前支持提取來源 IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意 Header(PARAM_PARSE_STRATEGY_HEADER)和任意 URL 參數(shù)(PARAM_PARSE_STRATEGY_URL_PARAM)四種模式。
    • fieldName:若提取策略選擇 Header 模式或 URL 參數(shù)模式,則需要指定對應(yīng)的 header 名稱或 URL 參數(shù)名稱。
    • pattern:參數(shù)值的匹配模式,只有匹配該模式的請求屬性值會納入統(tǒng)計和流控;若為空則統(tǒng)計該請求屬性的所有值。(1.6.2 版本開始支持)
    • matchStrategy:參數(shù)值的匹配策略,目前支持精確匹配(PARAM_MATCH_STRATEGY_EXACT)、子串匹配(PARAM_MATCH_STRATEGY_CONTAINS)和正則匹配(PARAM_MATCH_STRATEGY_REGEX)。(1.6.2 版本開始支持)

網(wǎng)關(guān)規(guī)則加載

用戶可以通過 GatewayRuleManager.loadRules(rules) 手動加載網(wǎng)關(guān)規(guī)則,或通過 GatewayRuleManager.register2Property(property) 注冊動態(tài)規(guī)則源動態(tài)推送(推薦方式)。

Spring cloud gateway

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

  • route 維度:即在 Spring 配置文件中配置的路由條目,資源名為對應(yīng)的 routeId
  • 自定義 API 維度:用戶可以利用 Sentinel 提供的 API 來自定義一些 API 分組

pom依賴

使用時引入如下模塊

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

注入實例到spring容器

使用時只需注入對應(yīng)的 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(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
}

同時定義了一些Api分組

private void initCustomizedApis() {
    Set<ApiDefinition> definitions = new HashSet<>();
    ApiDefinition api1 = new ApiDefinition("some_customized_api")
        .setPredicateItems(new HashSet<ApiPredicateItem>() {{
            add(new ApiPathPredicateItem().setPattern("/product/baz"));
            add(new ApiPathPredicateItem().setPattern("/product/foo/**")
                .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
        }});
    ApiDefinition api2 = new ApiDefinition("another_customized_api")
        .setPredicateItems(new HashSet<ApiPredicateItem>() {{
            add(new ApiPathPredicateItem().setPattern("/ahas"));
        }});
    definitions.add(api1);
    definitions.add(api2);
    GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}

Demo 示例:sentinel-demo-spring-cloud-gateway
比如我們在 Spring Cloud Gateway 中配置了以下路由:

server:
  port: 8090
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      enabled: true
      discovery:
        locator:
          lower-case-service-id: true
      routes:
        # Add your routes here.
        - id: product_route
          uri: lb://product
          predicates:
            - Path=/product/**
        - id: httpbin_route
          uri: https://httpbin.org
          predicates:
            - Path=/httpbin/**
          filters:
            - RewritePath=/httpbin/(?<segment>.*), /$\{segment}

那么這里面的 route ID(如 product_route)和 API name(如 some_customized_api)都會被標(biāo)識為 Sentinel 的資源。比如訪問網(wǎng)關(guān)的 URL 為 http://localhost:8090/product/foo/22 的時候,對應(yīng)的統(tǒng)計會加到 product_routesome_customized_api 這兩個資源上面,而 http://localhost:8090/httpbin/json 只會對應(yīng)到 httpbin_route 資源上面。

實驗

請求http://localhost:8090/product/foo/22

wrk -c 10 -t 10 -d 100 'http://localhost:8090/product/foo/22'

控制臺輸出

截屏2021-01-10 下午9.15.22.png
截屏2021-01-10 下午9.15.22.png

當(dāng)前url的請求路徑是"http://localhost:8090/product/foo/22",可以命中的資源有aliyun_route 和 some_customized_api,aliyun_route的規(guī)則定義是按照client_ip限流qps是2,aliyun_route資源qps是10。 some_customized_api(/ahas,/product/**)定義的規(guī)則是url需要包含參數(shù)pn。因為沒有指定參數(shù)pn,所以some_customized_api沒有拒絕qps,全部都通過了。

請求http://localhost:8090/product/foo/22?pn=11

wrk -c 10 -t 10 -d 100 'http://localhost:8090/product/foo/22?pn=11'

控制臺輸出


截屏2021-01-10 下午9.43.58.png
截屏2021-01-10 下午9.43.58.png

分析基本和上面一致,因為url的param里加了pn=11, 所以導(dǎo)致some_customized_api的資源變成了通過qps=5.0

請求http://localhost:8090/httpbin/json?pa=1

wrk -c 10 -t 10 -d 100 'http://localhost:8090/httpbin/json?pa=1'

sentinel dashboard輸出


截屏2021-01-10 下午10.00.22.png
截屏2021-01-10 下午10.00.22.png

這個url請求命中的規(guī)則是,資源名:httpbin_route, url參數(shù)需要有pa,限流閥值是1。

請求http://localhost:8090/httpbin/json 添加header

wrk -c 10 -t 10 -d 100 -H 'X-Sentinel-Flag: aa'  'http://localhost:8090/httpbin/json'

sentinel dashboard輸出


截屏2021-01-10 下午10.13.29.png
截屏2021-01-10 下午10.13.29.png

這個url請求命中的規(guī)則是,資源名:httpbin_route, url的請求頭header中需要有X-Sentinel-Flag,限流閥值是1。

請求http://localhost:8090/httpbin/json?type=warn

wrk -c 10 -t 10 -d 100 'http://localhost:8090/httpbin/json?type=warn'

sentinel dashboard輸出

截屏2021-01-10 下午10.35.55.png
截屏2021-01-10 下午10.35.55.png

這個url請求命中的規(guī)則是,資源名:httpbin_route, url的參數(shù)需要是type=warn,限流閥值是2。代碼中setIntervalSec設(shè)置的是30,我改成了1,如果是30的話,通過qps展示的是0,側(cè)面說明了setIntervalSec值越低,采樣越精確。

請求http://localhost:8090/httpbin/json?type=warn 添加header

wrk -c 10 -t 10 -d 100 -H 'X-Sentinel-Flag: aa'  'http://localhost:8090/httpbin/json?type=warn'
截屏2021-01-10 下午10.41.21.png
截屏2021-01-10 下午10.41.21.png

可以看到qps閥值是2,其實這個請求同時滿足了Header中包含X-Sentinel-Flag和type=warn,在代碼中X-Sentinel-Flag的定義在前面(代碼中使用了HashSet,我改成了LinkedHashSet)可以看到后面定義的規(guī)則覆蓋了前面的規(guī)則。

限流回調(diào)

您可以在 GatewayCallbackManager 注冊回調(diào)進行定制:

  • setBlockHandler:注冊函數(shù)用于實現(xiàn)自定義的邏輯處理被限流的請求,對應(yīng)接口為 BlockRequestHandler。默認(rèn)實現(xiàn)為 DefaultBlockRequestHandler,當(dāng)被限流時會返回類似于下面的錯誤信息:Blocked by Sentinel: FlowException。
private void initCallback(){
    GatewayCallbackManager.setBlockHandler(new DefaultBlockRequestHandler() {
        @Override
        public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
            t.printStackTrace();
            return super.handleRequest(exchange,t);
        }
    });
}

瀏覽器限流的輸出


截屏2021-01-10 下午10.54.57.png
截屏2021-01-10 下午10.54.57.png

控制臺限流輸出


截屏2021-01-10 下午10.57.01.png
截屏2021-01-10 下午10.57.01.png

Zuul 1.x

Sentinel 提供了 Zuul 1.x 的適配模塊,可以為 Zuul Gateway 提供兩種資源維度的限流:

  • route 維度:即在 Spring 配置文件中配置的路由條目,資源名為對應(yīng)的 route ID(對應(yīng) RequestContext 中的 proxy 字段)
  • 自定義 API 維度:用戶可以利用 Sentinel 提供的 API 來自定義一些 API 分組

使用時需引入以下模塊(以 Maven 為例):

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

若使用的是 Spring Cloud Netflix Zuul,我們可以直接在配置類中將三個 filter 注入到 Spring 環(huán)境中即可:

@Configuration
public class ZuulConfig {
    @Bean
    public ZuulFilter sentinelZuulPreFilter() {
        // We can also provider the filter order in the constructor.
        return new SentinelZuulPreFilter();
    }
    @Bean
    public ZuulFilter sentinelZuulPostFilter() {
        return new SentinelZuulPostFilter();
    }
    @Bean
    public ZuulFilter sentinelZuulErrorFilter() {
        return new SentinelZuulErrorFilter();
    }
}

Sentinel Zuul Adapter 生成的調(diào)用鏈路類似于下面,其中的資源名都是 route ID 或者自定義的 API 分組名稱:

-EntranceNode: sentinel_gateway_context$$route$$another-route-b(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:8 1mb:1 1mt:9)
--another-route-b(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:4 1mb:1 1mt:5)
--another_customized_api(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:4 1mb:0 1mt:4)
-EntranceNode: sentinel_gateway_context$$route$$my-route-1(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:6 1mb:0 1mt:6)
--my-route-1(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:2 1mb:0 1mt:2)
--some_customized_api(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:2 1mb:0 1mt:2)

發(fā)生限流之后的處理流程 :

  • 發(fā)生限流之后可自定義返回參數(shù),通過實現(xiàn) SentinelFallbackProvider 接口,默認(rèn)的實現(xiàn)是 DefaultBlockFallbackProvider
  • 默認(rèn)的 fallback route 的規(guī)則是 route ID 或自定義的 API 分組名稱。

比如:

// 自定義 FallbackProvider 
public class MyBlockFallbackProvider implements ZuulBlockFallbackProvider {
    private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class);
    
    // you can define route as service level 
    @Override
    public String getRoute() {
        return "/book/app";
    }
    @Override
        public BlockResponse fallbackResponse(String route, Throwable cause) {
            RecordLog.info(String.format("[Sentinel DefaultBlockFallbackProvider] Run fallback route: %s", route));
            if (cause instanceof BlockException) {
                return new BlockResponse(429, "Sentinel block exception", route);
            } else {
                return new BlockResponse(500, "System Error", route);
            }
        }
 }
 
// 注冊 FallbackProvider
ZuulBlockFallbackManager.registerProvider(new MyBlockFallbackProvider());

默認(rèn)情況下限流后會返回 429 狀態(tài)碼,返回結(jié)果為:

{
    "code":429,
    "message":"Sentinel block exception",
    "route":"/"
}

Zuul 2.x

注:從 1.7.2 版本開始支持,需要 Java 8 及以上版本。
Sentinel 提供了 Zuul 2.x 的適配模塊,可以為 Zuul Gateway 提供兩種資源維度的限流:

  • route 維度:對應(yīng) SessionContext 中的 routeVIP
  • 自定義 API 維度:用戶可以利用 Sentinel 提供的 API 來自定義一些 API 分組

使用時需引入以下模塊(以 Maven 為例):

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

然后配置對應(yīng)的 filter 即可:

filterMultibinder.addBinding().toInstance(new SentinelZuulInboundFilter(500));
filterMultibinder.addBinding().toInstance(new SentinelZuulOutboundFilter(500));
filterMultibinder.addBinding().toInstance(new SentinelZuulEndpoint());

可以參考 sentinel-demo-zuul2-gateway 示例。

實現(xiàn)原理

當(dāng)通過 GatewayRuleManager 加載網(wǎng)關(guān)流控規(guī)則(GatewayFlowRule)時,無論是否針對請求屬性進行限流,Sentinel 底層都會將網(wǎng)關(guān)流控規(guī)則轉(zhuǎn)化為熱點參數(shù)規(guī)則(ParamFlowRule),存儲在 GatewayRuleManager 中,與正常的熱點參數(shù)規(guī)則相隔離。轉(zhuǎn)換時 Sentinel 會根據(jù)請求屬性配置,為網(wǎng)關(guān)流控規(guī)則設(shè)置參數(shù)索引(idx),并同步到生成的熱點參數(shù)規(guī)則中。
外部請求進入 API Gateway 時會經(jīng)過 Sentinel 實現(xiàn)的 filter,其中會依次進行 路由/API 分組匹配、請求屬性解析參數(shù)組裝。Sentinel 會根據(jù)配置的網(wǎng)關(guān)流控規(guī)則來解析請求屬性,并依照參數(shù)索引順序組裝參數(shù)數(shù)組,最終傳入 SphU.entry(res, args) 中。Sentinel API Gateway Adapter Common 模塊向 Slot Chain 中添加了一個 GatewayFlowSlot,專門用來做網(wǎng)關(guān)規(guī)則的檢查。GatewayFlowSlot 會從 GatewayRuleManager 中提取生成的熱點參數(shù)規(guī)則,根據(jù)傳入的參數(shù)依次進行規(guī)則檢查。若某條規(guī)則不針對請求屬性,則會在參數(shù)最后一個位置置入預(yù)設(shè)的常量,達(dá)到普通流控的效果。

image.png
image.png

網(wǎng)關(guān)流控控制臺

在應(yīng)用啟動添加如下VM參數(shù)

-Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dcsp.sentinel.app.type=1

添加正確的啟動參數(shù)并有訪問量后,我們就可以在 Sentinel 上面看到對應(yīng)的 API Gateway 了。我們可以查看實時的 route 和自定義 API 分組的監(jiān)控和調(diào)用信息:


image.png
image.png

我們可以在控制臺配置自定義的 API 分組,將一些 URL 匹配模式歸為一個 API 分組:


image.png
image.png

然后我們可以在控制臺針對預(yù)設(shè)的 route ID 或自定義的 API 分組配置網(wǎng)關(guān)流控規(guī)則:
image.png
image.png

參考文檔

https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容