Spring Cloud Gateway与Spring Cloud Alibaba Sentinel 网关限流的实现

在前面我们知道Spring Cloud Gateway实现了一个RequestRateLimiter的过滤器,该过滤器会对访问到当前网关的所有请求执行限流过滤器、如果被限流,默认情况下回响应Http-429-Too Many Requests。RequestRateLimiterGatewayFilterFactory默认提供了RedisRateLimiter的限流实现,它采用令牌桶的算法实现限流功能。如下我们可以在Spring Cloud Gateway配置中配置过滤器—RequestRateLimiter:

spring:
  cloud:
    gateway:
      routes:
        - id: redis_limiter
          uri: https://example.org
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
server:
  port: 8081

redis-rate-Limiter有两个属性,分别为replenishRate表示令牌桶中令牌得到填充速度,代表允许每秒执行的请求数,burstCapacity表示令牌桶的容量,表示每秒用户最大执行的请求数。redis的限流是基于Stripe实现,需要加入下面的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

我们可以实现KeyResolver接口设置限流请求的key,指定对当前请求中的哪些因素进行限流,如下代码我们队请求的IP进行限流:

public class IpAddressKeyResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

 KeyResolver的默认实现为PrincipalNameKeyResolver,它会从ServerWebExchange 检索Principal并且调用Principal.getName方法。默认情况下如果KeyResolver没有获取到key,请求将会被拒绝,我们可以根据限流过滤器的参数调整分别为denyEmptyKey和emptyKeyStatus。如下为IP限流的配置:

spring:
  cloud:
    gateway:
      routes:
        - id: redis_limiter
          uri: https://example.org
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                denyEmptyKey: true
                emptyKeyStatus: SERVICE_UNAVAILABLE
                keyResolver: '#{@ipAddressKeyResolver}'
server:
  port: 8081

 我们调用接口时,redis会生成两个key分别为request_rater_limiter.{ip}.timestamp和request_rater_limiter.{ip}.tokens。Spring Cloud Gateway只实现了基于Redis的RateLimiter限流模式,我们可以通过实现AbstractRateLimiter自定义xianliuq,通过配置指定限流器:

rateLimiter:  #{@rateLimiterName}

Spring Cloud Alibaba Sentinel 从1.6版本提供了对Spring Cloud Gateway的支持,支持两种资源维度的限流,分别为Route维度和自定义API维度,可以利用提供的API来自定义API分组,然后针对这些组限流。我们需要引入Sentinel-Adapter依赖包:

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

在前面我们介绍Sentinel时,我们使用FlowRule提供限流规则,而在Spring Cloud Gateway 中Sentinel使用GatewayFlowRule作为限流规则。如下为一个简单的例子:

private void initGatewayRules() {
    Set<GatewayFlowRule> gatewayFlowRules = new HashSet<GatewayFlowRule>();
    GatewayFlowRule rule = new GatewayFlowRule().setCount(5).setIntervalSec(1).setResource("gateway-sentinel-limiter");
    gatewayFlowRules.add(rule);
    GatewayRuleManager.loadRules(gatewayFlowRules);
}

与FlowRule类似,GatewayFlowRule提供了以下属性:这里我们直接复制源码中的设置属性的部分代码并且注释,代码如下所示:

//设置资源名称,可以是网关配置中的route名称或者用户自定义的API分组
public GatewayFlowRule setResource(java.lang.String resource) {  }
//资源模型,限流针对API Gateway的route还是用户在sentinel定义的API分组
public GatewayFlowRule setResourceMode(int resourceMode) {  }
// 限流指标维度,同FlowRule的grade字段
public GatewayFlowRule setGrade(int grade) { }
//同FlowRule的controlBehavior字段,支持快速失败和匀速排队
public GatewayFlowRule setControlBehavior(int controlBehavior) { }
//限流阈值
public GatewayFlowRule setCount(double count) {  }
//统计时间窗口,单位为秒,默认是1秒
public GatewayFlowRule setIntervalSec(long intervalSec) { }
//应对突发请求时额外允许的请求数目
public GatewayFlowRule setBurst(int burst) { }
//匀速排队下最长排队时间
public GatewayFlowRule setMaxQueueingTimeoutMs(int maxQueueingTimeoutMs) { }
//参数限流配置,若不提供,则代表不针对参数进行限流。从请求中提取参数的策略,下面的类为GatewayParamFlowItem提供的属性
public GatewayFlowRule setParamItem(GatewayParamFlowItem paramItem) { }
public class GatewayParamFlowItem {
    private java.lang.Integer index;
    //解析策略:目前支持IP,HOST,任意Header和任意URL参数四种模式,
    private int parseStrategy;
    //为Header和URL策略时的Header 名称或者URL参数名称
    private java.lang.String fieldName;
    //下面的两个目前没有实现,后续可能会实现
    private java.lang.String pattern;
    private int matchStrategy;
}

Sentinel对Spring Cloud Gateway限流的支持其实是一个全局的过滤器,我们可以将全局过滤器注入到容器中,然后全局过滤器就会生效,如下为Sentinel在Spring Cloud Gateway中使用的配置:

Configuration
public class SentinelRateLimiterConfiguation {
    private final List<ViewResolver> viewResolverList;
    private final ServerCodecConfigurer serverCodecConfigurer;
    //注入视图解析器
    public SentinelRateLimiterConfiguation(ObjectProvider<List<ViewResolver>> viewResolverList, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolverList = viewResolverList.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }
    //注入Sentinel全局过滤器SentinelGatewayFilter
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
    //注入限流异常处理器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolverList, serverCodecConfigurer);
    }
    //初始化限流规则
    @PostConstruct
    public void initRules() {
        initGatewayRules();
    }

    private void initGatewayRules() {
        Set<GatewayFlowRule> gatewayFlowRules = new HashSet<GatewayFlowRule>();
        GatewayFlowRule rule = new GatewayFlowRule().setCount(5).setIntervalSec(1).setResource("gateway-sentinel-limiter");
        gatewayFlowRules.add(rule);
        GatewayRuleManager.loadRules(gatewayFlowRules);
    }
}

配置完Sentinel之后,我们需要配置Spring Cloud Gateway网关的相关配置,配置如下所示:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-sentinel-limiter
          uri: https://example.org
          predicates:
            -path=/sentinel/rate

前面我们说过,除了使用route限流之外,我们还可以自定义分组进行限流,实际上就是让多个route使用一个限流规则。加入我们需要以下两个route使用一个限流规则:

spring:
  cloud:
    gateway:
      routes:
        - id: exmaple-sentinel-limiter
          uri: https://example.org
          predicates:
            -path=/sentinel/rate
        - id: example1-sentinel-limiter
            uri: https://example1.org
            predicates:
              -path=/sentinel1/rate

下面我们可以将上面的两个路由配置到同一个分组,然后使用同一个限流规则,如下代码将上面的两个route定义到一个分组中:

private void initCustomizedApis() {
    ApiDefinition api = new ApiDefinition("customized-apis");
    Set<ApiPredicateItem> apiPredicateItems = new HashSet<ApiPredicateItem>();
    apiPredicateItems.add(new ApiPathPredicateItem().setPattern("/sentinel/rate"));
    apiPredicateItems.add(new ApiPathPredicateItem().setPattern("/sentinel1/rate").setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_REGEX));
    api.setPredicateItems(apiPredicateItems);
}

定义完分组之后,我们在定义限流规则时设置资源时即setResource()时,resource设置为customized-apis即可。代码如下:

private void initGatewayRules() {
        Set<GatewayFlowRule> gatewayFlowRules = new HashSet<GatewayFlowRule>();
        GatewayFlowRule rule = new GatewayFlowRule().setCount(5).setIntervalSec(1).setResource("customized-apis").setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME);
        gatewayFlowRules.add(rule);
        GatewayRuleManager.loadRules(gatewayFlowRules);
    }

猜你喜欢

转载自blog.csdn.net/wk19920726/article/details/108568218