TroubleShooting & Study/SpringBoot

[Rate Limiter] 트래픽 제어를 위한 처리율 제한 기능 (실전편 - Redis, Bucket4j)

DH_0518 2024. 9. 22. 22:59

이번에는 실제 서비스에서 처리율 제한 기능을 설정해 보겠다

 

1. Dependencies

 Sample Code를 작성했던 것처럼 서버 메모리에서 Bucket을 생성해서 관리할 수도 있지만, 서버가 재실행되면 Bucket의 정보가 날아가므로 대체제를 찾아야 한다

 먼저 Rate Limit을 걸어줄 API의 특징에 따라 달라지겠지만, 나는 유저마다 Rate Limit을 설정해 줄 것이기 때문에 'User'-'Bucket' 형태로 정보를 저장해야 했고, RDB와 Redis라는 선택지가 있었다

 마침 Bucket4j에서 Redis를 지원해주기도 했고, 각 유저가 API를 호출할 때 RDB에서 Bucket을 조회한다면 DB connection이 너무 많이 일어날 것 같기에 Redis를 선택하기로 했다

 

 

 

 

2. Configuration

 bucket4j-redis를 사용하기 위해서 Redis 설정 파일과 Bucket4j 설정 파일을 작성해 준다

 

 

 

Code

 

1. lettuceBasedProxyManager()

  • Redis 클라이언트 중 Lettuce를 사용하여 RedisProxyManager를 생성하는 메서드
  • Redis에서 key를 사용해 bucket을 crud 하는 RedisApi(eval, get, del)를 제공하는 역할을 한다고 보면 된다

 

2. StatefulRedisConnection

  • Redis에서 thread-safe 한 연결을 생성한다. redis와의 지속적인 연결 상태를 추적 및 관리하고, 데이터의 직렬/역직렬화, 그리고 Redis 명령을 실행하는 역할을 한다
  • RedisCodec.of(UTF8, ByteArrayCodec): Redis의 데이터를 저장하는 방식을 정의할 때, key를 UTF8로 value를 byte 배열로 저장

 

3. withExpirationStrategy(ExpirationAfterWriteStrategy)

  • Bucket의 만료 전략을 설정한다
    • calculateTimeToLiveMillis: 만료 시간을 커스텀하여, 동적으로 계산할 수 있다
    • fixedTimeToLive: 고정된 시간 이후 만료시킨다
    • basedOnTimeForRefillingBucketUpToMax: token이 refill 된 이후, 일정 시간 동안 사용되지 않으면 만료시킨다

4. BucketConfiguration

 

 

 

3. Filter

 다음은 실제로 유저의 limit을 확인하는 작업을 진행할 filter이다. 여기서 검사를 진행할 api를 선언하고, request가 해당 api라면 redis에 'UserUuid:Bucket' 으로 저장된 bucket을 조회하여 유저가 api를 호출할 수 있는지 확인한다

 

 

Code

: 코드는 크게 API별로 필터를 실행하는 메서드, 버킷을 조회하거나 생성하는 메서드, 토큰을 사용하는 메서드로 나뉜다. Request가 들어오면 Filter를 거치면서 bucket 검사를 해야 하는 API인지 확인한 후, Redis에서 'UserUUID'를 key로 유저의 Bucket을 가져오거나 생성한다. 이후에 토큰을 소모하는 데 성공하면 filter를 계속 진행시키고, 토큰이 부족해서 실패한다면 예외 처리를 해준다

 

 

 

1. CachingRequestWrapper

  • 서버로 넘어온 Request는 기본적으로 getReader()나 getInputStream()을 한 번만 호출할 수 있기 때문에, Request를 여러 번 읽어 들이기 위해 HttpServletRequestWrapper로 본문을 캐싱한 클래스

 

 

2. getCreateShortsBucket()

  • LettuceBasedProxyManager를 사용해서, Redis에 key에 해당하는 Bucket이 있다면 조회하고 없다면 생성해서 return 한다
  • 여기서는 유저 하나당 한 개의 bucket을 사용하기 위해 'UserUUID'를 key로 사용한다

 

3. consumeToken()

  • 실제로 토큰을 사용하는 메서드로, Bucket에서 'tryConsumeAndReturnRemaining()' 메서드를 사용하여 토큰을 소모하고 그 결과를 나타내는 ConsumptionProbe를 가져온다
  • probe를 통해 다음 결과를 얻을 수 있다
    • isConsumed(): 토큰 소비가 성공했는지 여부를 반환
    • getRemainingTokens(): 현재 버킷에 남은 토큰의 개수를 반환
    • getNanosToWaitForRefill(): 토큰 소비에 실패한 경우, 다음 리필까지 기다려야 하는 시간을 반환

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Reference

 

처리율 제한 장치 붙이기 여정

운영 업무만 처리하고 있던 기간에 처리율 제한 장치 업무가 들어왔다.팀에서 사용하지 않았던 기술이라 검토부터 적용까지 스스로 해나아간 과정들이 즐거워서 실수들이 있었지만 배포가 끝

velog.io

 

Bucket4j로 트래픽 제한하기(Redis & MariaDB)

개요 최근 업무 프로젝트에서 특정(요금이 부가되는) 로직에 대해 월별 사용량을 제한하는 기능이 추가되어야 했습니다. 이와 관련하여 처리율 제한 기술을 알아보았는데 Bucket4j, Guava, RateLimitj,

dkswnkk.tistory.com

 

Bucket4j 8.7.0 Reference

Question: Why does bucket invoke the listener on the client-side instead of the server-side in case of distributed scenario? What do I need to do if I need an aggregated stat across the whole cluster? Answer: Because of a planned expansion to non-JVM back-

bucket4j.com

 

[Spring Boot] request, response 한 번만 읽어올 수 있는 제약 해결

들어가며 프로젝트 진행 중 Client, Server Filter에서 Request, Response을 자동으로 암/복호화해주는 로직 개발을 맡았다. 자세한 내용은 아래 흐름도를 참고하면 된다. 그런데 개발 진행 중 문제가 발생

jangjjolkit.tistory.com