스프링 클라우드 게이트웨이에서 처리율 제한 라이브러리(Bucket4J)와 함께 RequestRateLimiterGatewayFilterFactory
를 사용하면 처리율 제한 필터를 간편하게 생성할 수 있습니다. 하지만 개발자가 처리율 제한 기능에 대한 접근은 지원하지 않습니다.
현재 실행중인 토큰 버킷에 접근하여 어느 정도 토큰 여유가 있는지 확인해야하는 기능이 필요한데 이 기능을 지원하지 않아 RequestRateLimiterGatewayFilterFactory
에 대해 탐구해보고 해당 기능을 개발하기 위한 정보를 알아보겠습니다.
public class RequestRateLimiterGatewayFilterFactory
extends AbstractGatewayFilterFactory<RequestRateLimiterGatewayFilterFactory.Config> {
private final RateLimiter defaultRateLimiter;
public RequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
super(Config.class);
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
}
...
}
RequestRateLimiterGatewayFilterFactory
는 스프링 클라우드 게이트웨이에서 기본적으로 제공하는 빌트인 게이트웨이 필터 팩토리입니다. RateLimiter
인터페이스를 구현한 클래스를 주입받아 처리율 제한 기능에 사용합니다.
public interface RateLimiter<C> extends StatefulConfigurable<C> {
Mono<Response> isAllowed(String routeId, String id);
class Response {
private final boolean allowed;
private final long tokensRemaining;
private final Map<String, String> headers;
public Response(boolean allowed, Map<String, String> headers) {
this.allowed = allowed;
this.tokensRemaining = -1;
Assert.notNull(headers, "headers may not be null");
this.headers = headers;
}
public boolean isAllowed() {
return allowed;
}
public Map<String, String> getHeaders() {
return Collections.unmodifiableMap(headers);
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Response{");
sb.append("allowed=").append(allowed);
sb.append(", headers=").append(headers);
sb.append(", tokensRemaining=").append(tokensRemaining);
sb.append('}');
return sb.toString();
}
}
}
RateLimiter
는 isAllowed(...)
메서드 하나만 가진 인터페이스입니다.RequestRateLimiterGatewayFilterFactory
에서는 RateLimiter
의 구현체에서 해당 요청이 통과 가능한가만 중요하기 때문에 RateLimiter
타입으로 빈을 주입받습니다.
public GatewayFilter apply(Config config) {
KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
RateLimiter<Object> limiter = getOrDefault(config.rateLimiter, defaultRateLimiter);
...
return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key -> {
...
return limiter.isAllowed(routeId, key).flatMap(response -> {
for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
}
if (response.isAllowed()) {
return chain.filter(exchange);
}
setResponseStatus(exchange, config.getStatusCode());
return exchange.getResponse().setComplete();
});
});
}
RequestRateLimiterGatewayFilterFactory
의 apply(...)
메서드에서 limiter의 isAllowed에 따라 동작이 구분되는 것을 확인할 수 있습니다.
Bucket4j란 처리율 제한 라이브러리 의존성으로 추가하고 디버깅을 실행 결과 RateLimiter
인터페이스의 구현체로 Bucket4jRateLimiter
가 연결되는 것을 확인할 수 있었습니다.
Bucket4jRateLimiter
는 RateLimiter
와 기타 클래스를 구현하고 상속하는 AbstractRateLimiter
를 상속하는 클래스입니다.
스프링 클라우드 게이트웨이에 따르면 Bucket4J를 사용해 처리율 제한 필터 팩터리를 이용한다면 AsyncProxyManager
타입을 빈으로 등록하라고 지시합니다.
Caffeine
클래스는 Java용 고성능 로컬 캐시 라이브러리로 ConcurrentHashMap
보다 빠르고 다양한 만료 정책을 지원합니다.