🪣 Bucket4j 란?
Bucket4j는 Token bucket 알고리즘을 기반으로 하는 Java 속도 제한 라이브러리이다.
트래픽을 제어하여 과도한 요청으로부터 서버의 자원을 보호할 수 있다.
- Token Bucket 알고리즘
- Token이 담긴 Bucket을 정의하고, 요청을 처리하는 비용으로 Token을 지불하는 방식으로 처리에 제한을 설정한 알고리즘이다.
- Bucket에 남겨진 Token이 부족하면 요청은 거절된다.
- Bucket에 Token은 일정 시간이 지나면 다시 채워진다.
🪣 Bucket4j 기능 소개
Bucket4j 라이브러리를 구성하고 있는 대표적인 클래스에 대해서 알아보자.
- Refill
- 일정시간마다 몇 개의 Token을 충전할지 지정하는 클래스이다. - Bandwidth
- Bucket의 총 크기를 지정하는 클래스이다. - Bucket
- 실제 트래픽을 제어하기 위해 사용되는 클래스이며, 앞에 설명한 Refill 클래스와 Bandwidth 클래스를 토대로 만들어진다.
🪣 Bucket4j 적용
- build.gradle
의존성을 명시해준다.
dependencies {
...
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.1.0'
...
}
- BucketService.java
동일한 사용자별로 반복 요청을 제어한다고 가정하에 사용자를 구분하기 위해서 요청 IP를 쓴다고 가정해 보자.
이를 위해 요청 헤더의 'Host' 값을 사용하며, 동일한 IP로 요청 시 일정 트래픽이 초과할 경우 요청을 거절할 예정이다.
버킷은 아래의 기준으로 생성된다.
- 버킷의 총 크기는 5이다.
- 버킷 안에 토큰은 10초마다 1개씩 재성성된다.
즉, 10초 안에 동일 사용자가 총 6번의 요청을 한다고 가정하면 5번째 요청에 대한 응답까지는 정상적으로 받을 수 있으나 6번째 응답에 대해서는 응답을 받을 수 없게 된다.
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class BucketService {
private final Map<String, Bucket> cache = new ConcurrentHashMap<>();
// 요청자 IP 추출
private String getHost(HttpServletRequest httpServletRequest) {
return httpServletRequest.getHeader("Host");
}
// 버킷 가져오기
public Bucket resolveBucket(HttpServletRequest httpServletRequest) {
return cache.computeIfAbsent(getHost(httpServletRequest), this::newBucket);
}
// 버킷 생성
private Bucket newBucket(String apiKey) {
return Bucket4j.builder()
// 버킷의 총 크기 = 5, 한 번에 충전되는 토큰 수 = 1, 10초마다 충전
.addLimit(Bandwidth.classic(5, Refill.intervally(1, Duration.ofSeconds(10))))
.build();
}
}
- BucketController.java
사용자 요청을 처리하는 컨트롤러이다. Bucket 기능에 중점을 두어 다른 비즈니스 로직은 없는 상태이다.
정상적인 요청의 경우,
@RequiredArgsConstructor
@Slf4j
@RestController
public class BucketController {
private final BucketService bucketService;
@GetMapping("/api/bucket/access")
public ResponseEntity bucketAccess(HttpServletRequest request) {
Bucket bucket = bucketService.resolveBucket(request);
log.info("접근 IP = {}", request.getRemoteAddr());
if (bucket.tryConsume(1)) { // 1개 사용 요청
return ResponseEntity.ok("[정상응답] 잔여토큰 : " + bucket.getAvailableTokens());
} else {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("[비정상응답] 트래픽 초과");
}
}
}
🪣 Bucket4j 적용 확인
프로젝트에 트래픽을 제어하는 기능이 정상적으로 구현되었는지 테스트해 보자.
테스트케이스와 기댓값은 아래와 같다.
- 첫 번째 요청에 대한 기댓값 : 정상 응답
- 두 번째 요청에 대한 기댓값 : 정상 응답
- 세 번째 요청에 대한 기댓값 : 정상 응답
- 네 번째 요청에 대한 기댓값 : 정상 응답
- 다섯 번째 요청에 대한 기댓값 : 정상 응답
- 여섯 번째 요청에 대한 기댓값 : 비정상 응답
- 일곱 번째 요청에 대한 기댓값 : 정상 응답 (설정된 토큰 재생성 시간인 10초가 지난 후에 요청 시)
References.
1. 히진쓰의 서버사이드 기술 블로그 - Bucket4j란? Bucket4j를 사용한 Spring 트래픽 제한하기
2. 개발자 되버리기 - SpringBoot 트래픽 제한하기
'Spring' 카테고리의 다른 글
[SpringBoot] 모니터링 환경 구축 #2 - Prometheus (0) | 2023.03.01 |
---|---|
[SpringBoot] 모니터링 환경 구축 #1 - Spring Actuator (0) | 2023.02.26 |
[SpringBoot] REST Docs (0) | 2023.02.19 |
[Security] JWT 구현 (0) | 2023.02.16 |
[Security] JWT 소개 (0) | 2023.02.12 |