У меня есть несколько микросервисов с Spring Cloud. Теперь мне нужно реализовать ограничение скорости для API на облачном шлюзе на основе имени пользователя.
С помощью KeyResolver я извлекаю пользователя из JWT-токена запрашивающей стороны. и реализовал RedisRateLimiter с тем же значением ограничения скорости (consumer50tps) для всех пользователей, как показано ниже. Он работает нормально.
Теперь требуется установить значения ограничения скорости на основе подписки пользователя, тогда как для одного и того же API/URL разные пользователи получают разные значения пределов (consumer50tps или Consumer100tps).
текущий код работает нормально для одного значения ограничения (потребитель 50 бит/с) для всех пользователей.
@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();
}
Простой способ сделать это — создать customFilter с помощью 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 для конкретного сервиса или API и наслаждайтесь им !!
@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();
}