我有一些带有 Spring Cloud 的微服务,现在我必须根据用户名对云网关上的 API 实现速率限制。
在 KeyResolver 的帮助下,我从请求者 JWT 令牌中提取用户 并为所有用户实现了具有相同速率限制值(consumer50tps)的 RedisRateLimiter,如下所示,工作正常。
现在的要求是我必须根据用户订阅设置速率限制值,而对于相同的 api/url ,不同的用户会获得不同的限制值(consumer50tps 或consumer100tps)。
当前代码对于所有用户的单个限制值(消费者 50tps)运行良好。
@Bean
@Primary
public RedisRateLimiter consumer50tps() {
return new RedisRateLimiter(50, 50, 1);
}
@Bean
public RedisRateLimiter consumer100tps() {
return new RedisRateLimiter(100, 100, 1);
}
@Bean
public KeyResolver userKeyResolver1() {
return exchange -> {
// Implement logic to fetch JWT and get user name
};
}
@Bean
public RouteLocator appRouteConfig(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p.path("/microservice-one/api1/**")
.filters(f -> f
.rewritePath("/microservice-one/(?<segment>.*)", "/${segment}")
.requestRateLimiter(r -> r.setRateLimiter(consumer50tps())
.setKeyResolver(userKeyResolver())
))
.uri("lb://microservice-one"))
.build();
}
我们可以在 GatewayFilter 的帮助下实现简单的方法:
@Component
public class MyRateLimiterFilter implements GatewayFilter {
private final UserKeyResolver userKeyResolver;
private final ReactiveRedisTemplate<String, String> redisTemplate;
public MyRateLimiterFilter(ReactiveRedisTemplate<String, String> redisTemplate, UserKeyResolver userKeyResolver) {
this.redisTemplate = redisTemplate;
this.userKeyResolver = userKeyResolver;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return userKeyResolver.resolve(exchange)
.flatMap(key -> {
return redisTemplate.opsForValue().increment(key)
.flatMap(tokens -> {
if (tokens == 1) return redisTemplate.expire(key, Duration.ofMillis(1000)).thenReturn(tokens);
return Mono.just(tokens);
})
.flatMap(tokens -> {
int maxReqPerSec = 1;
// Implement rate limit based on user here, in this case "key" returns the user ID from JWT.
// You can inject the user from anywhere like user-service or read from any properties file and set the limit !
if(key.equals("consumer50tps")){
maxReqPerSec = 50;
}else if(key.equals("consumer100tps")){
maxReqPerSec = 100;
}
if (tokens <= maxReqPerSec) {
return chain.filter(exchange);
} else {
LOGGER.info("Exceeded request limit for the user: "+key + ", current number of request: "+tokens);
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
});
});
}
}
然后将此过滤器添加到RouteLocator上并享受!!
@Bean
public RouteLocator appRouteConfig(RouteLocatorBuilder builder, MyRateLimiterFilter myRateLimiter) {
return builder.routes()
.route(p -> p.path("/microservice-one/**")
.filters(f -> f
.filter(myRateLimiter)
.rewritePath("/microservice-one/(?<segment>.*)", "/${segment}")
)
.uri("lb://microservice-one"))
.build();
}