초간단 Rate Limiting 구현(Bucket4j)
bucket4j
초간단 Rate Limiting 구현(Bucket4j)
API 서버를 운영하다 보면 클라이언트의 과도한 요청으로 인해 서버 리소스가 소진되는 문제를 방지해야 합니다. 이를 해결하기 위해 Rate Limiting(속도 제한) 기법이 사용되며, Java에서는 Bucket4j
라이브러리를 통해 효과적으로 구현할 수 있습니다.
이 글에서는 Bucket4j
의 개념과 Spring Boot에서의 활용 방법을 소개합니다.
Bucket4j란?
Bucket4j
는 토큰 버킷(Token Bucket) 알고리즘을 기반으로 동작하는 Java Rate Limiting 라이브러리입니다.
Resilience4j로도 rate limiing은 구현 가능하지만 간단하게 rate limiting 기능만 필요하다면 간단하게
Bucket4j
로도 사용 가능합니다.
주요 특징
- JVM 내에서 동작하는 순수 Java 구현 (추가적인 Redis, DB 필요 없음)
- 정확한 속도 제한을 제공 (토큰 버킷 기반)
- 클러스터 환경에서도 사용 가능 (Redis, Hazelcast 연동 지원)
- 필요에 따라 다양한 전략 적용 가능 (고정 율, 버스트 모드 등)
기본 개념: Token Bucket 알고리즘
Bucket4j
는 토큰 버킷(Token Bucket) 알고리즘을 사용합니다.
동작 방식
- 정해진 크기의 버킷(토큰 저장소)이 있음
- 일정한 속도로 토큰(Token)이 추가됨
- 요청이 들어오면 토큰을 소모하여 처리
- 토큰이 부족하면 요청이 차단됨
예를 들어, 초당 10개의 요청을 허용하는 API가 있다고 가정하면, Bucket4j
는 1초에 10개의 토큰을 추가하며, 토큰이 없으면 요청을 제한합니다.
Spring Boot에서 Bucket4j 적용하기
java17, springboot3.x기준으로 설명합니다. 전체 sample은 github-sample를 참조해주세요.
java8 버전은 여기를 참조해주세요.
dependency
1
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'
Rate Limiting 필터 구현
Rate limiting을 spring의 OncePerRequestFilter
를 사용하여 적용할 수 있습니다.
- filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import io.github.bucket4j.*;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final ConcurrentMap<String, Bucket> cache = new ConcurrentHashMap<>();
private Bucket createNewBucket() {
return Bucket4j.builder()
.addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1))))
.build();
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String ip = request.getRemoteAddr();
Bucket bucket = cache.computeIfAbsent(ip, k -> createNewBucket());
if (bucket.tryConsume(1)) {
filterChain.doFilter(request, response);
} else {
response.setStatus(429);
response.getWriter().write("Too Many Requests");
}
}
}
- RateLimiter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class RateLimiter {
private final ConcurrentMap<String, Bucket> rateLimitBuckets = new ConcurrentHashMap<>();
private Bucket createNewBucket(long capacity, long refill) {
return Bucket.builder()
.addLimit(limit -> limit.capacity(capacity).refillGreedy(refill, Duration.ofSeconds(1)))
.build();
}
public boolean isRequestAllowed(String key, String path) {
Bucket bucket = rateLimitBuckets.computeIfAbsent(key, v -> switch (path) {
case "/api/fast" -> createNewBucket(10, 10); // 초당 10개 요청 허용
case "/api/slow" -> createNewBucket(3, 3); // 초당 3개 요청 허용
case "/api/very-slow" -> createNewBucket(1, 1); // 초당 1개 요청 허용
default -> createNewBucket(5, 5); // 기본 초당 5개 요청 허용
});
return bucket.tryConsume(1);
}
}
ConcurrentHashMap<String, Bucket>
을 사용하여 IP별 요청 제한 적용Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1)))
→ 1분에 10개 요청 허용bucket.tryConsume(1)
→ 토큰이 있으면 요청 허용, 없으면429 Too Many Requests
응답 반환
외부 infra와 연동하여 Rate Limiting 적용
다중 인스턴스의 환경을 고려하여 외부 infra를 통해서 rate limiting을 설정할 수 있습니다.
결론
Bucket4j의 장점
- Spring Boot에서 간단하게 Rate Limiting 적용 가능
- JVM 내에서 동작하여 성능이 뛰어남
- Redis 등과 연동하여 클러스터 환경에서도 확장 가능
- IP별 제한, 사용자별 제한 등 다양한 정책 설정 가능
Bucket4j의 한계점
- 기본적으로 JVM 내에서 동작하므로, 서버가 재시작되면 데이터가 초기화됨
- 다중 인스턴스에서는 Redis 등의 저장소를 활용해야 함
- Spring Boot와 직접적인 통합 지원이 없어 Filter/Interceptor를 직접 구현해야 함
마무리
Bucket4j는 Spring Boot에서 간단하게 Rate Limiting을 구현할 수 있습니다.
[출처]
- https://bucket4j.com/
- https://github.com/bucket4j/bucket4j
- https://www.baeldung.com/spring-bucket4j
- https://dkswnkk.tistory.com/732