Why?
- 현재 담당하고 있는 서비스에서 API, DB 호출에 대한 초 당 처리량 조절 필요
Ratelimiter
Guava에서 제공하는 모듈로 초 당 처리량을 설정을 통하여 관리될 수 있도록 한다.
implementation("com.google.guava:guava")
- gradle 의존성 추가
1. 인스턴스 생성
private final RateLimiter apiRateLimiter = RateLimiter.create(4);
public static RateLimiter create(double permitsPerSecond) {
/*
* The default RateLimiter configuration can save the unused permits of up to one second. This
* is to avoid unnecessary stalls in situations like this: A RateLimiter of 1qps, and 4 threads,
* all calling acquire() at these moments:
*
* T0 at 0 seconds
* T1 at 1.05 seconds
* T2 at 2 seconds
* T3 at 3 seconds
*
* Due to the slight delay of T1, T2 would have to sleep till 2.05 seconds, and T3 would also
* have to sleep till 3.05 seconds.
*/
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
- 초 당 허용할 Task의 개수를 인자로 넘겨준다
public static RateLimiter create(double permitsPerSecond, Duration warmupPeriod) {
return create(permitsPerSecond, toNanosSaturated(warmupPeriod), TimeUnit.NANOSECONDS);
}
- 원격 서버를 바로 호출하지 않고 웜업이 필요한 경우 이러한 생성자를 사용할 수도 있음
2. 획득
허용 acquire() 을 획득하여야만 Task를 처리할 수 있음
public double acquire() {
return acquire(1);
}
@CanIgnoreReturnValue
public double acquire(int permits) {
long microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
예약 reserve()
final long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) {
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
- 예약은 허가 횟수(permits)를 예약하고, 대기 시간을 반환함
- 획득 acquire() 은 있지만 반납은 없다
주의 사항
내부적으로는 synchronized로 동작하여 권한 획득 시 대기 시간을 반환받고 대기 시간 이후 작업을 수행한다.
즉, 작업 권한을 부여받은 작업(Task)이 완료하여 종료되지 않더라도 다른 작업이 대기 시간 이후 작업을 수행할 수 있다.
Threadpool size를 고정적으로 사용하는 경우에는 큰 문제가 발생하지 않지만 동적으로 poolsize를 조절하는 newCachedThreadPool() 같은 경우 병목 현상이 발생한다면 최대 Integer.MAX_VALUE 만큼 thread가 생성될 수 있기에 유의하여 사용해야 한다.
'자바 개발자되기' 카테고리의 다른 글
구체적인 Exception Catch를 해야하는 이유 (0) | 2021.04.22 |
---|---|
Open Session In View와 트랜잭션, 그리고 영속성 컨텍스트 (0) | 2021.03.24 |
자바 원시(Primitive) 타입과 참조(Reference) 타입, 그리고 Reflection (1) | 2021.01.01 |
Spring @Transactional 어노테이션은 왜 Bean이 아닌 객체에서 적용되지 않는가? (0) | 2020.12.31 |
Junit5 @BeforeEach private method가 동작하는 이유 (0) | 2020.12.30 |