본문 바로가기
Java & Kotlin/Spring

Request Rate Limiting with Spring Cloud Gateway - 2. RequestRateLimiter 필터 적용

by devson 2022. 2. 3.

앞서 개요에서 Spring Cloud Gateway에서 기본적으로 Request Rate Limiting 기능을 제공한다고 하였다.

 

이번 포스팅에서는 Spring Cloud Gateway에서 기본적으로 제공하는 Request Rate Limiting 기능과 이를 사용하는 방법에 대해 알아보도록 하겠다.

 

전체 코드는 여기에서 확인 가능하다.


RequestRateLimiterFatewayFilterFactory in Spring Cloud Gateway

Spring Cloud Gateway에서 기본적으로 제공하는 Request Rate Limiting에 대한 필터는

org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory 이다.

 

그리고 이 필터는 내부적으로 아래 component를 사용한다.

그리고 이 두 component 모두 RequestRateLimiterGatewayFilterFactory.Config를 통해 받아올 수 있다.

(application.yml 설정으로 특정 component bean을 사용할 수 있다)

 

RequestRateLimiterGatewayFilterFactory를 사용하기 위해 application.yml에서 아래와 같이 설정한다.

spring:
  cloud:
    gateway:
      routes:
        - id: your route id
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
                redis-rate-limiter.requestedTokens: 1
                key-resolver: "#{@keyResolverBeanName}"

추가적으로 다른 인자를 설정할 수 있는데 일단 가장 자주 사용될 만한 인자들만 명시하였다.

각 인자들에 대해서 알아보자.

  • redis-rate-limiter
    • redis 라는 이름에서 보면 알 듯, Spring Cloud Gateway에서는 기본적으로 Redis를 사용하여
      Request Rate Limiting 기능을 구현하는데, 이는 다음 포스팅에서 다시 살펴보도록 하겠다.
    • Spring Cloud Gateway는 기본적으로 Redis를 사용하여 Token Bucket Algorithm을 구현하는데,
      이와 관련된 인자는 다음과 같다.
      • replenishRate: 초당 채워지는 Token의 비율
      • burstCapacity: 초기 Bucket 사이즈
      • requestedTokens: 요청을 처리할 때 소모되는 토큰의 수
  • key-resolver
    • Request Rate Limiting 시 요청에 대한 Key를 지정할 KeyResolver bean의 이름
    • SpEL을 사용하여 bean 이름을 지정한다.
      • 만약 bean name이 myKeyResolver라면 "#{@myKeyResolver}"로 설정하면 된다.

 

RequestRateLimiter적용

Spring Cloud Gateway에서 기본적으로 제공되는 RequestRateLimiter에 대해 알아보았다.

그럼 어떻게 설정하고 어떻게 적용하는지에 대해 알아보도록 하자.

 

이전 포스팅에서 API Gateway 서버에 인증 토큰을 통해 인증 처리를 하는 필터를 추가하였다.

이제 인증 필터에서 추출하여 넘겨준 User ID를 사용하여 Request Rate Limiting을 적용하도록 하겠다.

3에 해당하는 기능에 대해 추가해보자

 

KeyResolver 추가

앞서 만든 AuthFilter에서 넘겨준 User ID를 (위 시퀀스 다이어그램에서 2) 요청 제한을 위한 Key로써 사용하도록
KeyResolver를 구현한 custom KeyResolver를 만들어준다.

 

package com.tistory.devs0n.gateway.filters.ratelimit.resolver

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono

@Component("userIdAsKeyResolver")
class UserIdAsKeyResolver : KeyResolver {
    override fun resolve(exchange: ServerWebExchange): Mono<String> {
        val userId = exchange.request.headers.getFirst("X-USER-ID")!!
        return Mono.just(userId)
    }
}

 

Redis dependency 추가

앞서 Spring Cloud Gateway는 기본적으로 Redis를 사용한다고 하였다.

그렇기 때문에 Request Rate Limiting 기능을 사용하기 위해선 Spring Data Redis dependency를 추가하도록 해야한다.

Spring Cloud Gateway는 Webflux 기반이기 때문에 reactive로 추가해준다.

 

dependency {
    implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")
    // ...
}

 

application.yml 설정 추가

RequestRateFilter에 대한 설정과 앞서 추가한 Redis에 대한 설정을 추가해준다.

 

application.yml

server:
  port: 9000

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
  
  cloud:
    gateway:
      default-filters:
        - name: AuthFilter
          args:
            whiteList:
              chrisToken: chris

      routes:
        - id: all
          uri: http://localhost:8080
          predicates:
            - Path=/**

          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 20
                redis-rate-limiter.burstCapacity: 100
                redis-rate-limiter.requestedTokens: 3
                key-resolver: "#{@userIdAsKeyResolver}"

 

이제 설정은 끝이다.

위 설정에 맞게 Redis를 실행하고, API Gateway 서버와 API 서버를 실행하여 요청을 보내보자.

 

요청 테스트

 

이전 포스팅에서와 동일하게 인증 토큰을 Header에 담아 API Gateway 서버에 요청을 보내자.

등록된 인증토큰을 보낸 경우정상적으로 요청이 API 서버로 라우팅되는 것을 확인할 수 있다.

 

그럼 Redis에는 어떤 데이터가 저장돼있을까?

Redis에는 request_rate_limiter.{key}.tokens, request_rate_limiter.{key}.timestamp 이렇게 2개의 Key가 저장되어 있을 것이다.

 

request_rate_limiter.{key}.tokens

해당 key에 해당하는 buckettoken이 얼마나 남았는지를 저장한다.

- bucket size burstCapacity를 100으로 지정하고,

- 요청 시 사용되는 token의 수인 requestedTokens를 3으로 지정하였기 때문에

최초로 1회 요청을 보냈을 때 bucket에 남은 token은 100 - 3 = 97 이 되어야할 것이다.

 

 

request_rate_limiter.{key}.timestamp

가장 최근 요청의 timestamp를 저장한다.

 

이렇게 RequestRateLimiter 내부적으로 Redis를 사용하여 bucket의 token을 관리하여 Request Rate Limiting 기능을 하게된다.

 

(두 key에 TTL이 정해져있는데 이 시간은 어떻게 정해지는지에 대해서는 다음 포스팅에서 알아보도록 하겠다)

 

application.yml 설정 변경 후 초과 요청 테스트

정상 케이스 외에도 제한을 초과하여 요청을 보냈을 때는 어떻게 응답이 나오는지 알아보자.

 

쉽게 초과 요청을 시뮬레이션 할 수 있도록 redis-rate-limiter의 설정을 변경한다.

 

application.yml

server:
  port: 9000

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0

  cloud:
    gateway:
      default-filters:
        - name: AuthFilter
          args:
            whiteList:
              chrisToken: chris

      routes:
        - id: all
          uri: http://localhost:8080
          predicates:
            - Path=/**

          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
                redis-rate-limiter.requestedTokens: 1
#                redis-rate-limiter.replenishRate: 20
#                redis-rate-limiter.burstCapacity: 100
#                redis-rate-limiter.requestedTokens: 3
                key-resolver: "#{@keyAsUserIdResolver}"

 

설정을 바꾼 뒤 API Gateway 서버를 다시 실행하여 API를 여러 번 호출해보자.

그러면 아래와 같이 429 Too Many Requests 상태 코드와 Rate Limit 관련된 header가 응답으로 오는 것을 확인할 수 있을 것이다.

 


 

이번 포스팅에서 Spring Cloud Gateway에서 RequestRateFilter를 사용하여 Request Rate Limiting 기능을 적용하는 방법에 대해 알아보았다.

 

다음 포스팅에서는 RequestRateFilter가 내부적으로 어떻게 Request Rate Limiting을 처리하는지에 대해 조금 더 알아보도록 하겠다.

댓글