ThreadLocal and Resilience4j

2025. 3. 16. 17:48Spring Microservice

🚦 Resilience4j로 Rate Limiter 패턴 완벽히 구현하기 (with Spring  Cloud)

마이크로서비스 환경에서 서비스의 안정성과 가용성을 유지하는 핵심 전략 중 하나가 바로 속도 제한(Rate Limiting)입니다. 속도 제한은 일정 시간 내 처리 가능한 요청의 수를 제한해, 서비스가 과부하되는 것을 막습니다.

오늘은 Resilience4j 라이브러리를 이용한 Rate Limiter 패턴의 구현 방법을 소개합니다. 🔍🚀

 

1. Rate Limiter 패턴이란?

Rate Limiter 패턴은 지정된 시간 동안 서비스에 들어오는 요청 수를 제한하여, 과부하를 방지하는 설계 기법입니다.

📌 AtomicRateLimiter (디폴트)

  • 별도의 백그라운드 스레드를 추가하지 않고, 해당 요청을 처리하는 스레드가 직접 이 요청을 실행할 수 있는지 여부를 결정한다
  • 전체 시간을 나노초 단위로 분할하여 관리
  • 매우 효율적이며 성능이 뛰어남

📌 SemaphoreBasedRateLimiter

  • java.util.concurrent.Semaphore를 이용한 구현
  • 간단하지만 추가 스레드 관리가 필요

 

2. RateLimiter 설정 방법

아래는 bootstrap.yml을 통해 Resilience4j의 RateLimiter를 설정하는 예제입니다.

📋 bootstrap.yml 설정 예시

resilience4j.ratelimiter:
  instances:
    licenseService:
      timeoutDuration: 1000ms  # 권한을 기다리는 최대 시간 (디폴트값: 5초)
      limitRefreshPeriod: 5000 # 호출 권한 갱신 주기 (디폴트값: 500ns)
      limitForPeriod: 5        # 주기당 허용 가능한 최대 호출 수 (디폴트값: 50)

📚 주요 설정 파라미터 완벽 정리

  • timeoutDuration: 권한 대기 최대 시간 (디폴트값: 5초)
  • limitRefreshPeriod: 호출 권한 리셋 주기 (디폴트값: 500ns)
  • limitForPeriod: 주기당 최대 호출 수 (디폴트값: 50)

추가 설정 가능한 옵션:

  • intervalFunction: 호출 간격 동적 조정 함수
  • retryOnResultPredicate: 결과에 따른 재시도 판단 조건
  • retryOnExceptionPredicate: 예외 발생 시 재시도 조건
  • ignoreExceptions: 무시할 예외 목록 (디폴트값: Empty 목록)

 

3. 실제 서비스 코드에서 RateLimiter 패턴 구현 예시

다음은 서비스 메서드에 Resilience4j의 다양한 패턴을 함께 적용한 코드 예제입니다.

📋 서비스 메서드 RateLimiter 적용 코드

@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);
}
  • 하나의 메서드에 여러 Resilience4j 패턴(CircuitBreaker, Retry, Bulkhead, RateLimiter)을 동시에 적용 가능함
  • 각각의 fallback 메서드 지정 가능

 

4. RateLimiter와 Bulkhead의 명확한 차이점

패턴 역할 예시
Bulkhead 동시 호출 횟수 제한 (Concurrency) 동시에 10개의 호출까지만 허용
RateLimiter 시간당 전체 호출 횟수 제한 초당 100개의 호출까지만 허용

중요: 두 패턴을 동시에 적용하는 것도 가능하며, 요구사항에 따라 적절히 선택합니다.

 

5. ThreadLocal과 Resilience4j 활용 - Correlation ID 전파하기

서비스 간 요청을 추적하는 데는 Correlation ID가 필수적입니다.
아래는 Spring Filter를 통해 HTTP 헤더로부터 Correlation ID를 추출하고 ThreadLocal에 저장하는 예제입니다.

📋 UserContextFilter 클래스

@Component
public class UserContextFilter implements Filter {
    private static final Logger logger =
            LoggerFactory.getLogger(UserContextFilter.class);

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) 
                         throws IOException, ServletException {
        HttpServletRequest httpServletRequest =
                (HttpServletRequest) servletRequest;

        UserContextHolder.getContext().setCorrelationId(
                httpServletRequest.getHeader(UserContext.CORRELATION_ID));
        // 추가 헤더 저장(UserId, AuthToken 등)
        filterChain.doFilter(httpServletRequest, servletResponse);
    }
}

📋 UserContextHolder

public class UserContextHolder {
    private static final ThreadLocal<UserContext> userContext =
            new ThreadLocal<UserContext>();

    public static final UserContext getContext() {
        UserContext context = userContext.get();
        if (context == null) {
            context = createEmptyContext();
            userContext.set(context);
        }
        return userContext.get();
    }
    // setContext(), createEmptyContext() 메서드 포함됨
}

📋 UserContext 클래스

@Component
public class UserContext {
    public static final String CORRELATION_ID = "tmx-correlation-id";
    public static final String AUTH_TOKEN = "tmx-auth-token";
    public static final String USER_ID = "tmx-user-id";
    public static final String ORGANIZATION_ID = "tmx-organization-id";

    private String correlationId = new String();
    private String authToken = new String();
    private String userId = new String();
    private String organizationId = new String();

    // 모든 getter, setter 메서드 완벽 포함
}

 

 

6. 요청 시 Correlation ID 적용 예시 (Postman)

아래는 HTTP 요청 시 헤더에 Correlation ID를 설정하는 예시입니다:

  • 헤더 키: tmx-correlation-id
  • 값: TEST-CORRELATION-ID

출처 : Spring Microservices in Action 2nd Edition

 

7. 로깅 설정 추가 (application.yml 완벽 포함)

logging:
  level:
    org.springframework.web: WARN
    com.optimagrowth: DEBUG

 

 

📌 주의: ThreadLocal 사용 시 잘못된 처리는 메모리 누수(memory leak)를 유발할 수 있으므로 주의하여 사용합니다.

 

📖 Resilience4j 공식 문서 참고

다음 시간에는 Spring Cloud Gateway를 살펴보겠습니다. 감사합니다!

'Spring Microservice' 카테고리의 다른 글

Implementing the rate limiter pattern  (0) 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