IT

[SpringBoot] Bucket4j를 이용한 트래픽 제한

data-cloud 2025. 1. 18. 11:57
반응형

 

 

 

🪣 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 적용 확인

프로젝트에 트래픽을 제어하는 기능이 정상적으로 구현되었는지 테스트해 보자.
테스트케이스와 기댓값은 아래와 같다.

  1. 첫 번째 요청에 대한 기댓값 : 정상 응답
  2. 두 번째 요청에 대한 기댓값 : 정상 응답
  3. 세 번째 요청에 대한 기댓값 : 정상 응답
  4. 네 번째 요청에 대한 기댓값 : 정상 응답
  5. 다섯 번째 요청에 대한 기댓값 : 정상 응답
  6. 여섯 번째 요청에 대한 기댓값 : 비정상 응답
  7. 일곱 번째 요청에 대한 기댓값 : 정상 응답 (설정된 토큰 재생성 시간인 10초가 지난 후에 요청 시)

 

 

Case 1. 첫 번째 요청 - 성공

 

Case 2. 두 번째 요청 - 성공

 

Case 3. 세 번째 요청 - 성공

 

Case 4. 네 번째 요청 - 성공

 

Case 5. 다섯 번째 요청 - 성공

 

Case 6. 여섯 번째 요청 - 실패 [트래픽 초과]

 

Case 7. 일곱 번째 요청 - 성공

 

 

References.

1. https://caffeineoverflow.tistory.com/21

 

반응형

'IT' 카테고리의 다른 글

[SpringBoot] 모니터링 환경 구축 #2 - Prometheus  (4) 2025.01.18
[SpringBoot] 모니터링 환경 구축 #1 - Spring Actuator  (1) 2025.01.18
[SpringBoot] REST Docs  (2) 2025.01.18
[Security] JWT 구현  (1) 2025.01.18
[Security] JWT 소개  (2) 2025.01.18