스프링 클라우드 게이트웨이에서 처리율 제한 라이브러리(Bucket4J)와 함께 RequestRateLimiterGatewayFilterFactory 를 사용하면 처리율 제한 필터를 간편하게 생성할 수 있습니다. 하지만 개발자가 처리율 제한 기능에 대한 접근은 지원하지 않습니다.

현재 실행중인 토큰 버킷에 접근하여 어느 정도 토큰 여유가 있는지 확인해야하는 기능이 필요한데 이 기능을 지원하지 않아 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 인터페이스를 구현한 클래스를 주입받아 처리율 제한 기능에 사용합니다.

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();
		}

	}

}

RateLimiterisAllowed(...) 메서드 하나만 가진 인터페이스입니다.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();
		});
	});
}

RequestRateLimiterGatewayFilterFactoryapply(...) 메서드에서 limiter의 isAllowed에 따라 동작이 구분되는 것을 확인할 수 있습니다.

Bucket4jRateLimiter

Bucket4j란 처리율 제한 라이브러리 의존성으로 추가하고 디버깅을 실행 결과 RateLimiter 인터페이스의 구현체로 Bucket4jRateLimiter 가 연결되는 것을 확인할 수 있었습니다.

image.png

Bucket4jRateLimiterRateLimiter 와 기타 클래스를 구현하고 상속하는 AbstractRateLimiter 를 상속하는 클래스입니다.

AsyncProxyManager

스프링 클라우드 게이트웨이에 따르면 Bucket4J를 사용해 처리율 제한 필터 팩터리를 이용한다면 AsyncProxyManager 타입을 빈으로 등록하라고 지시합니다.

image.png