spring cloud gateway 限流

Last Modified: 2023/08/06

概述

为了防止恶意访问,我们需要对客户端限流,巧了,spring cloud 也内置了限流支持,今天我们一起看下 spring cloud 中如何对指定的服务限流。

限流配置方法

spring cloud 内置了网关过滤器工厂类 RequestRateLimiterGatewayFilterFactory 以提供对限流的支持。配置方法如下:

spring:
  cloud:
    gateway:
      routes:
        - id: route1
          uri: http://localhost:8081
          predicates:
            - Path=/order
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                redis-rate-limiter.requestedTokens: 1

默认的限流是基于 redis 的,这一点从 args 参数可以很容易看出,args 总共有三个子配置:

  • redis-rate-limiter.replenishRate
  • redis-rate-limiter.burstCapacity
  • redis-rate-limiter.requestedTokens

配置详解

要理解这三个参数,需要理解令牌桶算法:

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。当一段时间没有请求时,桶可能会被填满,填满后无法放入新的令牌,直到请求消耗了令牌时才能继续往桶中放入令牌。

对应到上面的配置参数,恒定的速率使用 replenishRate 配置,该配置表示每秒中往桶中放入多少个令牌,burstCapacity 则是桶的容量,requestedTokens 有点不同,表示一个请求消耗多少个令牌,一般为1。

至此我们完成了限流配置,如果此时访问该路由,发现请求被拒绝,打开浏览器控制台查看响应如下:

HTTP/1.1 403 Forbidden
content-length: 0

请求被拒绝的根本原因是无法定位限流对象,既然要限流,我们肯定要针对一个具体的对象限流,当无法确定限流对象的时候,spring cloud 默认拒绝请求。RequestRateLimiterGatewayFilterFactory 实现通过 KeyResolver 确定限流对象,默认的 KeyResolver 为 PrincipalNameKeyResolver,该 resolver bean 的定义在 GatewayAutoConfiguration 类中:

@Bean(name = PrincipalNameKeyResolver.BEAN_NAME)
@ConditionalOnBean(RateLimiter.class)
@ConditionalOnMissingBean(KeyResolver.class)
@ConditionalOnEnabledFilter(RequestRateLimiterGatewayFilterFactory.class)
public PrincipalNameKeyResolver principalNameKeyResolver() {
  return new PrincipalNameKeyResolver();
}

由于我们并没有登录,所以 PrincipalNameKeyResolver 无法确定用户身份,导致限流对象为空,因此请求被拒绝。但是我们可以通过配置让无法定位限流对象的这种情况不被限流,配置方法如下:

spring:
  cloud:
    gateway:
      filter:
        request-rate-limiter:
          deny-empty-key: false

KeyResolver

之前说道 KeyResolver 是为了确定限流对象,接口定义如下:

public interface KeyResolver {
  Mono<String> resolve(ServerWebExchange exchange);
}

我们可以完全自定义 KeyResolver,例如我们可以将用户 ip 作为限流对象,可以自定义一个 UserIpKeyResolver:

@Component
public class UserIpKeyResolver implements KeyResolver {
  @Override
  public Mono<String> resolve(ServerWebExchange exchange) {
    return Optional.ofNullable(exchange.getRequest().getRemoteAddress())
        .map(InetSocketAddress::getAddress)
        .map(InetAddress::getHostAddress)
        .map(Mono::just)
        .orElse(Mono.empty());
  }
}

如果希望直接在 spring boot 中使用限流,ratelimiter-spring-boot-starter

有问题吗?点此反馈!

温馨提示:反馈需要登录