토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

resilience4j 공식 레퍼런스를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

목차


Introduction

Rate limiting은 API 규모 확장에 대비하고, 서비스의 고가용성과 안정성을 확립하기 위해선 반드시 필요한 기술이다. 게다가 제한치를 넘어간 것을 감지했을 때의 동작이나 제한할 요청 타입과 관련된 광범위한 옵션을 함께 제공한다. 간단히 제한치를 넘어선 요청을 거부하거나, 큐를 만들어 나중에 실행할 수도 있고, 어떤 방식으로든 두 정책을 조합해도 된다.


Internals

Resilience4j는 epoch 시작 시점부터 나노초 단위로 사이클을 분할하는 RateLimiter를 제공한다. 각 사이클이 갖는 기간은 RateLimiterConfig.limitRefreshPeriod로 설정해둔 기간이다. 각 사이클이 시작될 때 RateLimiter는 RateLimiterConfig.limitForPeriod에 활성 권한 수를 설정한다.
RateLimiter를 호출하는 쪽에선 매 사이클마다 활성 권한 수를 갱신하는 것처럼 보이지만, 실제 AtomicRateLimiter 구현체 내부에선 RateLimiter 사용이 활발하지 않다면 갱신을 건너뛰는 식으로 최적화를 진행한다.

44ca055-rate_limiter

RateLimiter의 디폴트 구현체는 AtomicReference를 통해 상태를 관리하는 AtomicRateLimiter다. AtomicRateLimiter.State는 완전한 immutable 클래스로, 다음과 같은 필드를 가지고 있다:

다른 구현체로는, 세마포어를 사용하면서 RateLimiterConfig#limitRefreshPeriod가 지날 때 마다 스케줄러를 통해 권한을 리프레시하는 SemaphoreBasedRateLimiter가 있다.


Create a RateLimiterRegistry

이 모듈은 CircuitBreaker 모듈과 동일하게 RateLimiter 인스턴스들을 관리(생성/조회)할 수 있는 인 메모리 RateLimiterRegistry를 제공한다.

RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();

Create and configure a RateLimiter

커스텀 글로벌 RateLimiterConfig를 제공하는 것도 가능하다. 커스텀 글로벌 RateLimiterConfig를 생성할 땐 RateLimiterConfig 빌더를 사용하면 된다. 이 빌더를 통해 다음과 같은 프로퍼티를 설정할 수 있다.

Config property Default value Description
timeoutDuration 5 [s] 스레드가 권한 획득을 기다리는 디폴트 시간
limitRefreshPeriod 500 [ns] 제한을 갱신할 주기. rate limiter는 이 시간만큼 지날 때마다 권한 수를 limitForPeriod 값으로 되돌린다.
limitForPeriod 50 제한을 갱신한 후 재갱신 전까지 사용할 수 있는 권한 수

예를 들어 어떤 메소드를 호출하는 속도를 10req/ms 이하로 제한하고 싶다면 다음과 같이 설정하면 된다.

RateLimiterConfig config = RateLimiterConfig.custom()
  .limitRefreshPeriod(Duration.ofMillis(1))
  .limitForPeriod(10)
  .timeoutDuration(Duration.ofMillis(25))
  .build();

// Create registry
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.of(config);

// Use registry
RateLimiter rateLimiterWithDefaultConfig = rateLimiterRegistry
  .rateLimiter("name1");

RateLimiter rateLimiterWithCustomConfig = rateLimiterRegistry
  .rateLimiter("name2", config);

Decorate and execute a functional interface

짐작했겠지만, RateLimiter도 CircuitBreaker에 있던 고차원 데코레이터 함수를 모두 가지고 있다. Callable, Supplier, Runnable, Consumer, CheckedRunnable, CheckedSupplier, CheckedConsumer, CompletionStage라면 모두 RateLimiter로 데코레이트할 수 있다.

// Decorate your call to BackendService.doSomething()
CheckedRunnable restrictedCall = RateLimiter
    .decorateCheckedRunnable(rateLimiter, backendService::doSomething);

Try.run(restrictedCall)
    .andThenTry(restrictedCall)
    .onFailure((RequestNotPermitted throwable) -> LOG.info("Wait before call it again :)"));

changeTimeoutDurationchangeLimitForPeriod 메소드를 사용해서 런타임에 rate limiter 파라미터를 변경할 수 있다.
바뀐 타임아웃 값은 현재 권한을 기다리고 있는 스레드엔 영향을 주지 않는다.
새로운 제한치도 현재 주기에서 획득할 수 있는 권한엔 영향을 주지 않으며, 다음 주기부터 적용된다.

// Decorate your call to BackendService.doSomething()
CheckedRunnable restrictedCall = RateLimiter
    .decorateCheckedRunnable(rateLimiter, backendService::doSomething);

// during second refresh cycle limiter will get 100 permissions
rateLimiter.changeLimitForPeriod(100);

Consume emitted RegistryEvents

RateLimiterRegistry에 이벤트 컨슈머를 등록해서 RateLimiter가 생성, 교체, 삭제될 때마다 필요한 로직을 실행할 수 있다.

RateLimiterRegistry registry = RateLimiterRegistry.ofDefaults();
registry.getEventPublisher()
  .onEntryAdded(entryAddedEvent -> {
    RateLimiter addedRateLimiter = entryAddedEvent.getAddedEntry();
    LOG.info("RateLimiter {} added", addedRateLimiter.getName());
  })
  .onEntryRemoved(entryRemovedEvent -> {
    RateLimiter removedRateLimiter = entryRemovedEvent.getRemovedEntry();
    LOG.info("RateLimiter {} removed", removedRateLimiter.getName());
  });

Consume emitted RateLimiterEvents

RateLimiter는 RateLimiterEvent 스트림을 방출한다. 이벤트는 권한 획득 성공일 수도 있고, 획득 실패일 수도 있다.
모든 이벤트는 이벤트 생성 시간, rate limiter 이름과 같은 추가 정보를 가지고 있다.
이 이벤트를 컨슘하려면 이벤트 컨슈머를 등록해야 한다.

rateLimiter.getEventPublisher()
    .onSuccess(event -> logger.info(...))
    .onFailure(event -> logger.info(...));

RxJava나 RxJava2, Project Reactor 어댑터를 사용하면 EventPublisher를 리액티브 스트림으로 전환할 수 있다.

ReactorAdapter.toFlux(rateLimiter.getEventPublisher())
    .filter(event -> event.getEventType() == FAILED_ACQUIRE)
    .subscribe(event -> logger.info(...))

Override the RegistryStore

인 메모리 RegistryStore는 커스텀 구현체로 재정의할 수 있다. 예를 들어 일정 시간이 지나면 사용하지 않는 인스턴스를 제거하는 캐시를 사용하고 싶다면:

RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.custom()
  .withRegistryStore(new CacheRateLimiterRegistryStore())
  .build();

전체 목차는 여기에 있습니다.

<< >>

TOP