2025. 3. 16. 17:34ㆍSpring Microservice
🚀 [Spring Cloud + Resilience4j] Rate Limiter(속도 제한) 패턴 가이드 (서비스 과부하 방지하기)
지난 포스팅에서 우리는 Resilience4j의 Circuit Breaker, Retry, Bulkhead, Fallback 패턴을 깊이 있게 다뤘습니다. 이번 포스팅에서는 서비스의 호출 속도를 제한하여 과부하를 방지할 수 있는 Rate Limiter(속도 제한) 패턴에 대해 자세히 알아보겠습니다! 📌✨
⏱️ Rate Limiter 패턴이란 무엇인가?
Rate Limiter 패턴은 지정된 시간 내 서비스가 처리할 수 있는 요청 수를 제한하는 방식입니다. 이를 통해 API가 갑작스러운 트래픽 증가로 인해 과부하 상태가 되거나 서비스 장애가 발생하지 않도록 보호할 수 있습니다. 이는 현대 클라우드 환경에서 API의 안정성과 가용성을 높이는 필수적인 방법입니다. 💡
📝 참고: 최신 클라우드 아키텍처에서는 자동 확장(autoscaling)이 효과적이지만, 이 글에서는 해당 주제를 다루지 않습니다.
🔍 Resilience4j의 두 가지 Rate Limiter 구현 방식
Resilience4j는 두 가지 Rate Limiter 구현 방식을 제공합니다:
- AtomicRateLimiter (디폴트값)
- SemaphoreBasedRateLimiter
1️⃣ SemaphoreBasedRateLimiter 방식 🔗
- 자바의
java.util.concurrent.Semaphore
를 사용하여 간단하게 제한을 설정합니다. - 각 사용자 스레드는
semaphore.tryAcquire()
를 호출하여 허가(permission)를 얻고, 제한 시간이 갱신되면semaphore.release()
를 호출하여 다시 허가를 부여합니다. - 별도의 내부 스레드를 사용하여 권한을 관리하는 방식입니다.
2️⃣ AtomicRateLimiter 방식 (디폴트값) 🚧
- 추가적인 스레드 관리가 필요 없습니다.
- 각 호출 주기를 나노초 단위로 나누어 권한을 관리합니다.
- 나노초 단위 주기마다 사용 가능한 허가(권한)를 리프레시합니다.
다음의 세 가지 주요 개념을 이해하면 AtomicRateLimiter를 쉽게 이해할 수 있습니다.
- ActiveCycle: 마지막 호출에서 사용된 주기 번호
- ActivePermissions: 마지막 호출 후 남은 허가(permissions) 수
- NanosToWait: 마지막 호출에서 허가를 받기까지 기다려야 할 시간(나노초)
📌 RateLimiter 설정 시 고려해야 하는 Resilience4j 개념들
- 주기(cycle)는 동일한 시간 단위로 나눠진다.
- 권한이 부족하면 예약(reservation)을 수행하여 권한을 미리 확보한다.
- 호출 허용 횟수(
limitForPeriod
), 갱신 주기(limitRefreshPeriod
), 최대 대기 시간(timeoutDuration
)을 설정할 수 있다.
🛠️ Rate Limiter 설정 방법 (bootstrap.yml 예시)
Resilience4j의 RateLimiter를 구성하는 방법은 다음과 같습니다:
📋 Listing 7.10 bootstrap.yml 설정 예시
resilience4j.ratelimiter:
instances:
licenseService:
timeoutDuration: 1000ms # 권한 대기 최대 시간 (기본: 5초)
limitRefreshPeriod: 5000 # 권한 갱신 주기 (기본: 500ns)
limitForPeriod: 5 # 한 주기 내 허용할 최대 호출 수 (기본: 50)
🔖 각 파라미터의 의미와 기본값
파라미터 | 설명 | 디폴트값 |
---|---|---|
timeoutDuration |
호출 허가를 기다리는 최대 시간 | 5초 |
limitRefreshPeriod |
호출 권한 갱신 주기 | 500ns |
limitForPeriod |
한 주기 내 허용 가능한 호출 수 | 50개 |
🎯 서비스 메서드에 RateLimiter 적용 예시
아래는 Resilience4j에서 제공하는 다양한 패턴을 결합하여 사용한 예제입니다.
📋 실제 서비스 코드 (LicenseService.java)
@CircuitBreaker(name = "licenseService", fallbackMethod = "buildFallbackLicenseList")
@RateLimiter(name = "licenseService", fallbackMethod = "buildFallbackLicenseList")
@Retry(name = "retryLicenseService", fallbackMethod = "buildFallbackLicenseList")
@Bulkhead(name = "bulkheadLicenseService", fallbackMethod = "buildFallbackLicenseList")
public List<License> getLicensesByOrganization(String organizationId) throws TimeoutException {
logger.debug("getLicensesByOrganization Correlation id: {}",
UserContextHolder.getContext().getCorrelationId());
randomlyRunLong(); // 장애 시뮬레이션 메서드
return licenseRepository.findByOrganizationId(organizationId);
}
🔑 어노테이션 설명
@RateLimiter
는 메서드 호출의 속도를 제한합니다.- 지정된 시간(
limitRefreshPeriod
) 동안 허용된 최대 호출 수(limitForPeriod
)를 초과하면 추가 요청을 제한합니다. - 실패할 경우 지정된 폴백(
fallbackMethod
)이 호출됩니다.
📌 Bulkhead vs Rate Limiter – 무엇이 다를까요? 🤔
Bulkhead 패턴과 Rate Limiter 패턴은 비슷해 보이지만 중요한 차이가 있습니다:
패턴 | 핵심 목적 | 예시 |
---|---|---|
Bulkhead | 동시 호출(concurrent calls) 제한 | "최대 10개까지 동시 호출 허용" |
Rate Limiter | 특정 시간 내 총 호출 수 제한 | "1분에 최대 100개 호출 허용" |
👉 서비스의 어떤 부분을 보호할 것인지에 따라 적합한 패턴을 선택하거나, 두 패턴을 결합하여 사용할 수도 있습니다.
🚩 RateLimiter를 선택할 때 유의점
- 제한값(
limitForPeriod
)을 지나치게 낮게 설정하면 사용자 경험이 나빠질 수 있습니다. - 대기시간(
timeoutDuration
)을 너무 길게 설정하면 병목현상이 발생할 수 있습니다. - 운영환경의 실제 사용량을 기준으로 최적화된 설정을 찾는 것이 매우 중요합니다.
📎 참고 링크
더 자세한 Resilience4j Rate Limiter 옵션은 공식 문서를 참고하세요.
🔗 Resilience4j RateLimiter 공식 문서
🔥 결론 및 마무리
이제 Resilience4j의 Rate Limiter 패턴을 활용하여 API를 더욱 안정적이고 견고하게 운영할 수 있게 되었습니다. 🚀🎉
다음 글에서는 Resilience4j의 추가적인 활용법과 운영 노하우를 계속해서 소개하겠습니다. 감사합니다! 🙌✨
'Spring Microservice' 카테고리의 다른 글
ThreadLocal and Resilience4j (1) | 2025.03.16 |
---|---|
Implementing the retry pattern (0) | 2025.03.16 |
99 퍼센타일(percentile) (0) | 2025.03.16 |
Implementing the bulkhead pattern (1) | 2025.03.16 |
Fallback processing (0) | 2025.03.16 |