본문 바로가기

엔지니어링

API 게이트웨이 - Rate limit

지난 번에는 API 게이트웨이에서의 인증에 대해 살펴봤는데요, 이번에는 rate limit에 대해 알아보겠습니다.

rate limit은 간단히 말하면 서비스에 대한 요청 수를 제한하는 기술입니다. 일정 시간 동안 특정 클라이언트(또는 전체 시스템)가 서버에 보낼 수 있는 요청 수를 제한함으로써, 서비스가 과부하 상태로 진입하는 것을 방지합니다.

이렇게 말해서는 와닿지 않으실 겁니다. 제가 rate limit이 왜 필요한지 예시를 들어보겠습니다.

 

 

상상해봅시다. 여러분이 카페의 사장입니다. 훌륭한 바리스타들과 맛있는 커피를 보유하고 있죠. 하지만 한 고객이 들어와서 1분에 한 잔씩 커피를 주문하기 시작했습니다. 매우 열심히 일하고 있는데도 불구하고, 이 고객의 요청을 처리하는 동안 다른 고객들은 커피를 받지 못하고 긴 줄에 서 있어야 합니다.

 

이런 상황이 계속되면 다른 고객들은 서비스가 느리다고 불평하고, 카페를 떠날 수도 있습니다. 이런 상황을 해결하기 위해서 우리는 시간당 커피 주문 횟수를 제한하는 정책을 도입할 수 있습니다. 예를 들어, 10분당 한 잔의 커피만 주문할 수 있도록 하는 것이죠. 이런 방식으로 모든 고객에게 공정하게 서비스를 제공하고 카페의 안정성을 유지할 수 있습니다.

 

API의 Rate Limit도 이와 같은 원리로 작동합니다. 한 사용자가 API를 과도하게 요청하거나 나아가 악의적인 공격(예: DDoS)을 하면 시스템 오버로드가 발생하거나 다른 사용자들의 요청을 처리할 수 없게 됩니다. 이를 방지하기 위해 API 요청 횟수를 제한하게 되는데, 이게 바로 Rate Limiting입니다. 이를 통해 서비스의 안정성을 유지하고 모든 사용자에게 공정한 사용 환경을 제공할 수 있습니다.

 

그럼 이 Rate Limit 정책을 어디에 붙여놓는 게 좋을까요? API 게이트웨이가 마이크로서비스로 가는 요청을 제일 앞에서 먼저 받고, 그 요청을 적절한 마이크로서비스로 라우팅하는 역할을 하기 때문에 API 게이트웨이에 rate limit을 적용해 사용자의 요청 수를 제어하는 것이 좋겠죠.

 

Spring Cloud Gateway를 이용한 Rate Limit

Spring Cloud Gateway는 rate limit 기능을 지원합니다. Spring Cloud Gateway에서 rate limit을 설정해보겠습니다. application.yml에서 설정할 수 있는데요.

spring:
  cloud:
    gateway:
      routes:
       -id: my_route
        uri: lb://my_service
        predicates:
        -Path=/my_path/**
        filters:
        -name: RequestRateLimiter
         args:
           redis-rate-limiter.replenishRate: 10
           redis-rate-limiter.burstCapacity: 20

 

`predicates:`에 이 라우트에 적용될 조건들을 설정할 수 있습니다. 여기서는 요청의 경로가 '/my_path/'로 시작하는 경우에만 이 라우트를 사용하도록 조건을 추가했습니다.

 

`filters:` 에 위 경로로 갈 요청들을 필터링하는 rate limit을 설정합니다.

 

위 설정에서 `redis-rate-limiter.replenishRate`는 초당 요청 수를, `redis-rate-limiter.burstCapacity`는 처리할 수 있는 요청의 최대량을 설정합니다. 이 설정에 따라 클라이언트는 초당 최대 10개의 요청을 보낼 수 있으며, 이 한도를 초과하면 요청은 제한됩니다.

 

그런데 uri가 lb로 시작하는 것이 좀 의아하실 수 있는데요. 단순하게 `uri: http://my_service`로 설정할 수도 있지만 Spring Cloud의 로드 밸런싱 기능을 사용하기 위해서 `lb://my_service`로 설정하였습니다.

 

`lb://my_service` URI를 사용하려면 Spring Cloud Discovery Client와 함께 동작하는 서비스 디스커버리 시스템이 필요합니다. 서비스 디스커버리 시스템은 서비스의 물리적 위치나 인스턴스 개수에 대해 신경 쓸 필요 없이 해당 서비스에 요청을 보낼 수 있도록 해주는 시스템인데요.

 

여기서는 서비스 디스커버리 서비스 중 하나인 Eureka를 사용하여 `my_service`를 등록하고 구성해 보겠습니다.

 

1. 우선 my_service 애플리케이션에 Eureka Client 종속성을 추가합니다.

implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'

2. `my_service` 애플리케이션의 application.yml에 Eureka 서버의 위치를 설정합니다.

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

3. `my_service` 애플리케이션의 메인 클래스에 `@EnableDiscoveryClient` 어노테이션을 추가합니다.

 

4. Eureka 서버가 실행되고, `my_service` 애플리케이션이 Eureka에 등록되면, Spring Cloud Gateway 에서 `lb://my_service`를 사용해 해당 서비스에 대한 요청을 로드 밸런싱할 수 있습니다.

 

그럼 다음 포스팅에서 서비스 디스커버리 패턴에 대해 알아보겠습니다.