처리율 제한 장치(rate limi**ter)**는 클라이언트 또는 서비스가 보내는 트래픽의 처리율을 제어하기 위한 장치이다.
HTTP를 예로 들면 이 장치는 특정 기간 내에 전송되는 클라이언트의 요청 횟수를 제한한다.
장점
- DoS 공격에 의한 자원 고갈을 방지
- 비용 절감
- 서버 과부화 방지
처리율 제한 알고리즘
- 토큰 버킷
- 누출 버킷
- 고정 윈도 카운터
- 이동 윈도 로그
- 이동 윈도 카운터
분산 환경에서 고려할 점
- 경쟁 조건(race condition)
- 분산 환경에서 요청 카운트를 공유자원으로 사용함으로 값이 잘 못 될 수 있다.
- 루아 스크립트 또는 레디스 자료구조인 정렬 집합을 활용해 해결할 수 있다.
- 동기화(synchronization)
- 처리율 제한 장치 서버를 여러 대 두게 되면 동기화가 필요하다.
- 레디스와 같은 중앙 집중형 데이터 저장소를 사용해 해결할 수 있다.
토큰 버킷 기반 처리율 제한 장치 구현 해보기 (gpt 도움)
package ch04_rate_limiter;
import java.util.concurrent.atomic.AtomicInteger;
public class TokenBucketRateLimiter {
private final int maxTokens; // 최대 토큰 수
private final int refillRate; // 초당 리필되는 토큰 수
private final long refillIntervalInMillis; // 리필 간격 (밀리초 단위)
private final AtomicInteger currentTokens; // 현재 토큰 수
private volatile long lastRefillTimestamp; // 마지막 리필 시점
public TokenBucketRateLimiter(int maxTokens, int refillRate, long refillIntervalInMillis) {
this.maxTokens = maxTokens;
this.refillRate = refillRate;
this.refillIntervalInMillis = refillIntervalInMillis;
this.currentTokens = new AtomicInteger(maxTokens);
this.lastRefillTimestamp = System.currentTimeMillis();
}
// 요청 시 토큰 리필 및 소비
public boolean tryAcquire() {
refillTokensIfNeeded();
if (currentTokens.get() > 0) {
currentTokens.decrementAndGet();
return true;
} else {
return false;
}
}
// 필요한 경우 토큰 리필
private void refillTokensIfNeeded() {
long now = System.currentTimeMillis();
long elapsedTime = now - lastRefillTimestamp;
if (elapsedTime > refillIntervalInMillis) {
int tokensToAdd = (int) (elapsedTime / refillIntervalInMillis * refillRate);
int newTokenCount = Math.min(maxTokens, currentTokens.get() + tokensToAdd);
currentTokens.set(newTokenCount); // 토큰 갱신
lastRefillTimestamp = now; // 리필 시점 갱신
}
}
public static void main(String[] args) throws InterruptedException {
// 최대 5개의 토큰, 초당 2개 리필
TokenBucketRateLimiter rateLimiter = new TokenBucketRateLimiter(5, 2, 1000);
// 테스트 시뮬레이션
Runnable requestTask = () -> {
if (rateLimiter.tryAcquire()) {
System.out.println("Request processed at " + System.currentTimeMillis());
} else {
System.out.println("Request denied at " + System.currentTimeMillis());
}
};
// 200ms 간격으로 요청
for (int i = 0; i < 50; i++) {
Thread thread = new Thread(requestTask);
thread.start();
Thread.sleep(200);
}
}
}
private volatile long lastRefillTimestamp
멀티 스레드 환경에서 스레드가 리필 시간을 가져올 때 가시성을 보장하기 위해 volatile을 사용
private final AtomicInteger currentTokens;
토큰 개수를 락없이 스레드 세이프하게 사용하기 위해 AtomicInteger 사용
처음 토큰 5개를 소진한 후 바로 토큰 2개를 보충한 것으로 보인다.
그 이후 일정 시간마나 2개씩 토큰을 소진하는 것을 볼 수 있다.
'시스템 설계 > 가상 면접 사례로 배우는 대규모 시스템 설계 기초' 카테고리의 다른 글
6장. 키-값 저장소 설계 (0) | 2025.01.24 |
---|---|
5장. 안정 해시 설계 (0) | 2025.01.20 |
3장. 시스템 설계 면접 공략법 (1) | 2025.01.12 |
2장. 개략적인 규모 추정 (0) | 2025.01.12 |
1장. 사용자 수에 따른 규모 확장성 (1) | 2025.01.12 |
댓글