【云原生&微服务>SCG网关篇十二】Spring Cloud Gateway集成Sentinel API实现多种限流方式

文章目录

  • 一、前言
  • 二、Gateway集成Sentinel API
    • 0、集成Sentinel的核心概念
      • 1)GatewayFlowRule 和 ApiDefinition
      • 2)GatewayFlowRule字段解释
    • 1、针对Route维度限流
      • 验证
    • 2、针对API维度限流
      • 验证
    • 3、自定义限流异常返回值
      • 验证
  • 三、总结

一、前言

至此微服务网关系列文章已出:

  1. 【云原生&微服务>SCG网关篇一】为什么要有网关、生产环境如何选择网关
  2. 云原生&微服务>SCG网关篇二】生产上那些灰度发布方式
  3. 【云原生&微服务>SCG网关篇三】Spring Cloud Gateway是什么、详细使用案例
  4. 云原生&微服务>SCG网关篇四】Spring Cloud Gateway内置的11种PredicateFactory如何使用
  5. 【云原生&微服务>SCG网关篇五】Spring Cloud Gateway自定义PredicateFactory
  6. 【云原生&微服务>SCG网关篇六】Spring Cloud Gateway内置的18种Filter使用姿势
  7. 【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试
  8. 【云原生&微服务>SCG网关篇八】Spring Cloud Gateway三种自定义Filter、GlobalFilter的方式
  9. 【云原生&微服务>SCG网关篇九】Spring Cloud Gateway集成Nacos详细案例
  10. 【云原生&微服务>SCG网关篇十】Spring Cloud Gateway集成Actuator、Zipkin详细案例
  11. 【云原生&微服务>SCG网关篇十一】Spring Cloud Gateway解决跨域问题

聊了以下问题:

  1. 为什么要有网关?网关的作用是什么?
  2. 网关的分类?
  3. 网关的技术选型?
  4. 使用网关时常用的灰度发布方式有哪些?
  5. Spring Cloud Gateway是什么?详细使用案例?
  6. Spring Cloud Gateway内置的11种PredicateFactory
  7. 如何自定义PredicateFactory?
  8. Spring Cloud Gateway内置的18种常用的Filter
  9. Spring Cloud Gateway基于内置Filter实现限流、熔断、重试
  10. Spring Cloud Gateway三种自定义Filter、GlobalFilter的方式
  11. Spring Cloud Gateway集成Nacos案例
  12. Spring Cloud Gateway集成Actuator、Zipkin案例
  13. Spring Cloud Gareway如何解决CORS跨域问题

我们已经聊过了Spring Cloud Gateway的一种限流方式:使用内置的Filter(RequestRateLimiterGatewayFilterFactory)结合Redis使用令牌桶算法实现限流;这里我们再聊一下另外一种:Spring Cloud Gateway集成Sentinel实现限流。

PS:SpringCloud版本信息:

<properties>
    <spring-boot.version>2.4.2spring-boot.version>
    <spring-cloud.version>2020.0.1spring-cloud.version>
    <spring-cloud-alibaba.version>2021.1spring-cloud-alibaba.version>
properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-dependenciesartifactId>
            <version>${spring-boot.version}version>
            <type>pomtype>
            <scope>importscope>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-dependenciesartifactId>
            <version>${spring-cloud.version}version>
            <type>pomtype>
            <scope>importscope>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-alibaba-dependenciesartifactId>
            <version>${spring-cloud-alibaba.version}version>
            <type>pomtype>
            <scope>importscope>
        dependency>
    dependencies>
dependencyManagement>

二、Gateway集成Sentinel API

sentinel服务1.6.0以上的版本可支持整合到网关进行统一流控:

sentinel提供了两种资源维度的限流:

  • route维度:在配置文件中配置路由,资源名为对应的 routeId,一般是对某个微服务进行限流;这种维度属于粗粒度的限流。
  • 自定义API维度:通过Sentinel 提供的API来自定义一些API分组,针对某一类的uri进行匹配限流,可以跨多个微服务;这种维度属于细粒度的限流。

gateway整合sentinel默认不支持 URL 粒度限流;因此通过 Spring Cloud Alibaba 接入时,将spring.cloud.sentinel.filter.enabled 配置为 false,以关闭流控控制台上的 URL 资源视图。

gateway整合Sentinel的maven依赖:

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

0、集成Sentinel的核心概念

整体集成原理如下:
【云原生&微服务>SCG网关篇十二】Spring Cloud Gateway集成Sentinel API实现多种限流方式_第1张图片

1)GatewayFlowRule 和 ApiDefinition

网关的限流规则GatewayFlowRule:

  • 针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流;
    用户自定义的API分组ApiDefinition:
  • ApiDefinition可以看做是一些 URL 匹配的组合;限流的时候可以针对这个自定义的 API 分组进行限流。

2)GatewayFlowRule字段解释

网关限流规则 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) 注册规则(推荐方式);

特别注意:当使用 Spring Cloud Alibaba Sentinel 数据源模块时,需要注意网关流控规则数据源类型是 gw-flow,若将网关流控规则数据源指定为 flow 则不生效。

application.yml配置文件:

spring:
  application:
    name: nacos-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 106.15.139.143:8848
    gateway:
      discovery:
        locator:
          # 开启从注册中心动态创建路由的功能
          enabled: true
          # 是否使用service-id的小写,默认是大写
          lower-case-service-id: true
      routes:
        - id: gateway-nacos-service-route
          # 其中配置的lb://表示从注册中心获取服务,后面的gateway-nacos-provider表示目标服务在注册中心上的服务名
          uri: lb://gateway-nacos-provider
          predicates:
            - Path=/nacos/sentinel/** #被限流
          filters:
            - StripPrefix=2
        # 自定义Sentinel API分组限流
        - id: red_route
          uri: lb://gateway-nacos-provider
          predicates:
            - Path=/red/** #被限流
          filters:
            - StripPrefix=1
        - id: green_route
          uri: lb://gateway-nacos-provider
          predicates:
            - Path=/green/** #不被限流
          filters:
            - StripPrefix=1

下面的三个样例均依赖此application.yml配置文件。

1、针对Route维度限流

搞一个ConfigurationClass,在类初始化的最后阶段(@PostConstruct标注的方法)实例化一个GatewayFlowRule,并将其添加到GatewayRuleManager中;GatewayFlowRule中指定针对哪个Route限流、限流的规则是什么?

package com.saint.gateway.sentinel;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;

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 com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
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;

/**
 * 针对某个Route限流
 *
 * @author Saint
 */
@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolvers.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 注入一个全局限流过滤器SentinelGatewayFilter
     * Filter的优先级最高
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    /**
     * 注入限流异常处理器
     * Filter的优先级最高
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /**
     * 初始化限流规则
     */
    @PostConstruct
    public void doInit() {
        initGatewayRules();
    }

    /**
     * Route维度限流规则
     */
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        // 这里表示1s仅允许通过一个请求,GatewayFlowRule构造函数中的如参为路由名
        GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("gateway-nacos-service-route").setCount(1).setIntervalSec(1);
        rules.add(gatewayFlowRule);
        GatewayRuleManager.loadRules(rules);
    }
}

当前案例表示针对routeId为gateway-nacos-service-route的路由做限流:1s仅允许一个请求通过;

验证

【云原生&微服务>SCG网关篇十二】Spring Cloud Gateway集成Sentinel API实现多种限流方式_第2张图片

当请求被限流时会返回429状态码,响应体内容为:

{"code":429,"message":"Blocked by Sentinel: ParamFlowException"}

2、针对API维度限流

自定义API分组限流,将/nacos/sentinel/**/red/**进行统一分组,并提供name=saint_customized_api,然后在初始化网关限流规则时,针对该name设置限流规则;

package com.saint.gateway.sentinel;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;

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 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.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;

/**
 * 自定义API分组限流
 *
 * @author Saint
 */
@Configuration
public class GatewayConfiguration1 {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration1(ObjectProvider<List<ViewResolver>> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolvers.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 注入SentinelGatewayFilter
     * Filter的优先级最高
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    /**
     * 注入限流异常处理器
     * Filter的优先级最高
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /**
     * 初始化限流规则
     */
    @PostConstruct
    public void doInit() {
        initCustomizedApis();
        initGatewayRules();
    }

    /**
     * 自定义API分组限流,将/nacos/sentinel/**和/red/**进行统一分组,并提供name=saint_customized_api,然后在初始化网关限流规则时,针对该name设置
     * 限流规则。同时,可以通过setMatchStrategy来设置不同path下的限流参数策略
     */
    private void initCustomizedApis() {
        Set<ApiDefinition> definitions = new HashSet<>();
        // 自定义针对API限流的ApiDefinition,apiName可以随便取,在GatewayFlowRule中填入即可。
        ApiDefinition apiDefinition = new ApiDefinition("saint_customized_api");
        // 匹配下面请求路径的请求将被限流
        apiDefinition.setPredicateItems(new HashSet<ApiPredicateItem>() {
            {
                add(new ApiPathPredicateItem().setPattern("/nacos/sentinel/**")
                        .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                add(new ApiPathPredicateItem().setPattern("/red/**")
                        .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
            }
        });
        definitions.add(apiDefinition);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

    /**
     * 针对分组name来设置限流规则
     */
    private void initGatewayRules() {
        // 针对ApiDefinition进行限流
        GatewayFlowRule rule = new GatewayFlowRule("saint_customized_api")
                .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME).setCount(1)
                .setIntervalSec(1);
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(rule);
        GatewayRuleManager.loadRules(rules);
    }
}

验证

【云原生&微服务>SCG网关篇十二】Spring Cloud Gateway集成Sentinel API实现多种限流方式_第3张图片

当我们访问/nacos/sentinel/**/red/**路径时才会被限流,其他路径均不会。

3、自定义限流异常返回值

Sentinel默认的异常处理器是com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler,我们可以根据这个Handler自定义一个WebExceptionHandler实现类。

1> 自定义WebExceptionHandler实现类:

package com.saint.gateway.sentinel;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.function.Supplier;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 异常处理器
 */
public class MySentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
    private List<ViewResolver> viewResolvers;
    private List<HttpMessageWriter<?>> messageWriters;
    private final Supplier<ServerResponse.Context> contextSupplier = () -> {
        return new ServerResponse.Context() {
            public List<HttpMessageWriter<?>> messageWriters() {
                return MySentinelGatewayBlockExceptionHandler.this.messageWriters;
            }

            public List<ViewResolver> viewResolvers() {
                return MySentinelGatewayBlockExceptionHandler.this.viewResolvers;
            }
        };
    };

    public MySentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers,
                                                  ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolvers;
        this.messageWriters = serverCodecConfigurer.getWriters();
    }

    /**
     * 该方法的作用是,将限流的异常信息写回客户端
     */
    private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-type", "application/json;charset=UTF-8");
        byte[] datas = "{\"code\":999,\"msg\":\"访问人数太多了,让我歇歇吧\"}".getBytes();
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }

    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        if (exchange.getResponse().isCommitted()) {
            return Mono.error(ex);
        } else {
            return !BlockException
                    .isBlockException(ex) ? Mono.error(ex)
                    : this.handleBlockedRequest(exchange, ex).flatMap((response) -> this.writeResponse(response, exchange));
        }
    }

    private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
        return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
    }
}

2> 将MySentinelGatewayBlockExceptionHandler注入到Spring容器中:

// 注入自定义的限流异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public MySentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
    return new MySentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}

验证

接着根据API分组限流的案例进行验证;

在这里插入图片描述

响应的状态码是200,body是我们自定义的body:

{"code":999,"msg":"访问人数太多了,让我歇歇吧"}

三、总结

sentinel提供了两种资源维度的限流:

  • route维度:在配置文件中配置路由,资源名为对应的 routeId,一般是对某个微服务进行限流;这种维度属于粗粒度的限流。
  • 自定义API维度:通过Sentinel 提供的API来自定义一些API分组,针对某一类的uri进行匹配限流,可以跨多个微服务;这种维度属于细粒度的限流。

本文的演示案例基于文章()

PS:Sentinel整合Nacos配置源:

spring:
  cloud: #配置SpringCloudGateway的路由
    sentinel:
      transport:
        dashboard: ip:port   #sentinel控制台的请求地址
      datasource:
        ds1:
          nacos:
            server-addr: ip:port
            data-id: ${spring.application.name}-${spring.profiles.active}-sentinel-gw-flow
            group-id: DEFAULT_GROUP
            data-type: json
            # 流控规则
            rule-type: gw-flow
        ds2:
          nacos:
            server-addr: ip:port
            data-id: ${spring.application.name}-${spring.profiles.active}-sentinel-gw-api-group
            group-id: DEFAULT_GROUP
            data-type: json
            # api类型
            rule-type: gw-api-group
      eager: true #立即加载
      log:
        # 日志存放目录
        dir: /data/sentinel/gateway

引入maven依赖:

<dependency>
      <groupId>com.alibaba.cloudgroupId>
      <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
      <groupId>com.alibaba.cloudgroupId>
      <artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>

你可能感兴趣的:(微服务,服务网关SCG,微服务,云原生,sentinel)