spring cloud gateway 限流
概述
为了防止恶意访问,我们需要对客户端限流,巧了,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。
温馨提示:反馈需要登录