Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Bucket4J 및 Redis를 사용하여 확장 가능한 속도 제한 구현 – 단계별 가이드

Bucket4J 및 Redis를 사용하여 확장 가능한 속도 제한 구현 – 단계별 가이드

이 튜토리얼에서는 확장된 서비스에서 속도 제한을 구현하는 방법을 알아봅니다.
이를 구현하기 위해 Bucket4J 라이브러리를 사용할 것이며 Redis를 분산 캐시로 사용할 것입니다.

비율 제한을 사용하는 이유

속도 제한의 필요성을 이해하고 이 튜토리얼에서 사용할 도구를 소개하기 위해 몇 가지 기본 사항부터 시작하겠습니다.

무제한 요금 문제

Twitter API와 같은 공개 API를 사용하여 사용자가 시간당 무제한 요청을 허용할 경우 다음과 같은 결과가 발생할 수 있습니다.

  • 리소스 고갈
  • 서비스 품질 저하
  • 서비스 거부 공격

이로 인해 서비스를 사용할 수 없거나 속도가 느려지는 상황이 발생할 수 있습니다. . 또한 예상치 못한 비용이 더 많이 발생할 수도 있습니다. 서비스로 인해 발생하는 비용입니다.

비율 제한이 도움이 되는 방법

첫째, 속도 제한은 서비스 거부 공격을 방지할 수 있습니다. 중복 제거 메커니즘 또는 API 키와 결합하면 속도 제한을 통해 분산 서비스 거부 공격을 방지할 수도 있습니다.

둘째, 트래픽을 추정하는 데 도움이 됩니다. 이는 공개 API에 매우 중요합니다. 이는 서비스를 모니터링하고 확장하기 위해 자동화된 스크립트와 결합될 수도 있습니다.

셋째, 이를 사용하여 계층 기반 가격 책정을 구현할 수 있습니다. 이러한 유형의 가격 책정 모델은 사용자가 더 높은 비율의 요청에 대해 비용을 지불할 수 있음을 의미합니다. Twitter API가 이에 대한 예입니다.

토큰 버킷 알고리즘

토큰 버킷은 속도 제한을 구현하는 데 사용할 수 있는 알고리즘입니다. 간단히 말해서 다음과 같이 작동합니다:

  1. 버킷은 특정 용량(토큰 수)으로 생성됩니다.
  2. 요청이 들어오면 버킷을 확인합니다. 용량이 충분하면 요청을 진행할 수 있습니다. 그렇지 않으면 요청이 거부됩니다.
  3. 요청이 허용되면 용량이 줄어듭니다.
  4. 일정 시간이 지나면 용량이 채워집니다.

분산 시스템에서 토큰 버킷을 구현하는 방법

분산 시스템에서 토큰 버킷 알고리즘을 구현하려면 분산 캐시를 사용해야 합니다. .

캐시는 키-값 저장소입니다. 버킷 정보를 저장합니다. 이를 구현하기 위해 Redis 캐시를 사용할 것입니다.

내부적으로 Bucket4j를 사용하면 Java JCache API의 모든 구현을 연결할 수 있습니다. Redis의 Redisson 클라이언트가 우리가 사용할 구현입니다.

프로젝트 구현

우리는 Spring Boot 프레임워크를 사용하여 서비스를 구축할 것입니다.

우리 서비스에는 다음 구성요소가 포함됩니다:

  1. 간단한 REST API.
  2. Redisson 클라이언트를 사용하여 서비스에 연결된 Redis 캐시.
  3. REST API를 래핑한 Bucket4J 라이브러리.
  4. Redisson 클라이언트를 백그라운드 구현으로 사용하는 JCache 인터페이스에 Bucket4J를 연결하겠습니다.

먼저 모든 요청에 대해 API의 속도를 제한하는 방법을 알아봅니다. 그런 다음 사용자별 또는 가격 책정 계층별로 보다 복잡한 속도 제한 메커니즘을 구현하는 방법을 알아봅니다.

프로젝트 설정부터 시작해 보겠습니다.

종속성 설치

pom.xml에 아래 종속성을 추가해 보겠습니다. (또는 build.gradle ) 파일입니다.

<dependencies>
 <!-- To build the Rest API -->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <!-- Redisson Starter = Spring Data Redis starter(excluding other clients) and Redisson client -->
 <dependency>
 <groupId>org.redisson</groupId>
 <artifactId>redisson-spring-boot-starter</artifactId>
 <version>3.17.0</version>
 </dependency>
 <!-- Bucket4J starter = Bucket4J + JCache -->
 <dependency>
 <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
 <artifactId>bucket4j-spring-boot-starter</artifactId>
 <version>0.5.2</version>
 </dependency>
</dependencies>

캐시 구성

먼저 Redis 서버를 시작해야 합니다. 로컬 시스템의 포트 6379에서 실행되는 Redis 서버가 있다고 가정해 보겠습니다.

우리는 두 단계를 수행해야 합니다:

  1. 우리 애플리케이션에서 이 서버에 대한 연결을 만듭니다.
  2. Redisson 클라이언트를 구현으로 사용하도록 JCache를 설정합니다.

Redisson의 문서는 일반 Java 애플리케이션에서 이를 구현하기 위한 간결한 단계를 제공합니다. 우리는 동일한 단계를 구현할 것이지만 Spring Boot에서 구현할 것입니다.

먼저 코드를 살펴보겠습니다. 필요한 빈을 생성하려면 구성 클래스를 생성해야 합니다.

@Configuration
public class RedisConfig {
 @Bean
 public Config config() {
 Config config = new Config();
 config.useSingleServer().setAddress("redis://localhost:6379");
 return config;
 }
 @Bean
 public CacheManager cacheManager(Config config) {
 CacheManager manager = Caching.getCachingProvider().getCacheManager();
 cacheManager.createCache("cache", RedissonConfiguration.fromConfig(config));
 return cacheManager;
 }
 @Bean
 ProxyManager<String> proxyManager(CacheManager cacheManager) {
 return new JCacheProxyManager<>(cacheManager.getCache("cache"));
 }
}

이것은 무엇을 합니까?

  1. 연결을 생성하는 데 사용할 수 있는 구성 개체를 생성합니다.
  2. 구성 개체를 사용하여 캐시 관리자를 만듭니다. 그러면 내부적으로 Redis 인스턴스에 대한 연결이 생성되고 해당 인스턴스에 "캐시"라는 해시가 생성됩니다.
  3. 캐시에 액세스하는 데 사용할 프록시 관리자를 만듭니다. 우리 애플리케이션이 JCache API를 사용하여 캐시하려고 시도하는 것은 무엇이든 "cache"라는 해시 내부의 Redis 인스턴스에 캐시됩니다.

API 구축

간단한 REST API를 만들어 보겠습니다.

@RestController
public class RateLimitController {
 @GetMapping("/user/{id}")
 public String getInfo(@PathVariable("id") String id) {
 return "Hello " + id;
 }
}

URL http://localhost:8080/user/1로 API를 실행하면 , Hello 1 응답을 받게 됩니다. .

Bucket4J 구성

속도 제한을 구현하려면 Bucket4J를 구성해야 합니다. 다행히도 시작 라이브러리 덕분에 상용구 코드를 작성할 필요가 없습니다.

또한 ProxyManager 빈을 자동으로 감지합니다. 이전 단계에서 생성하여 이를 사용하여 버킷을 캐시합니다.

우리가 해야 할 일은 우리가 만든 API를 중심으로 이 라이브러리를 구성하는 것입니다.
이를 수행하는 방법에는 여러 가지가 있습니다.

시작 라이브러리에 정의된 속성 기반 구성을 사용할 수 있습니다.
이는 모든 사용자 또는 모든 게스트 사용자에 대한 속도 제한과 같은 간단한 경우에 가장 편리한 방법입니다.

그러나 각 사용자에 대한 비율 제한과 같이 더 복잡한 것을 구현하려면 이에 대한 사용자 정의 코드를 작성하는 것이 좋습니다.

사용자당 속도 제한을 구현할 예정입니다. 데이터베이스에 저장된 각 사용자에 대한 속도 제한이 있고 사용자 ID를 사용하여 이를 쿼리할 수 있다고 가정해 보겠습니다.

이에 대한 코드를 단계별로 작성해 보겠습니다.

버킷 만들기

시작하기 전에 버킷이 어떻게 생성되는지 살펴보겠습니다.

Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(10, refill);
Bucket bucket = Bucket4j.builder()
 .addLimit(limit)
 .build();
  • 리필 – 버킷은 얼마 후에 다시 채워질 예정입니까?
  • 대역폭 – 버킷의 대역폭이 얼마나 됩니까? 기본적으로 리필 기간당 요청수입니다.
  • 버킷 – 이 두 매개변수를 사용하여 구성된 개체입니다. 또한 버킷에서 사용 가능한 토큰 수를 추적하기 위해 토큰 카운터를 유지 관리합니다.

이를 기본 구성 요소로 사용하여 사용 사례에 적합하도록 몇 가지 사항을 변경해 보겠습니다.

ProxyManager를 사용하여 버킷 생성 및 캐시

Redis에 버킷을 저장하기 위해 프록시 관리자를 만들었습니다. 버킷이 생성되면 Redis에 캐시되어야 하며 다시 생성할 필요가 없습니다.

이를 위해 Bucket4j.builder()를 교체합니다. proxyManager.builder()로 . ProxyManager는 버킷 캐싱을 처리하고 버킷을 다시 생성하지 않습니다.

ProxyManager의 빌더는 라는 두 가지 매개변수를 사용합니다. 버킷이 캐시될 대상과 구성 개체 버킷을 생성하는 데 사용됩니다.

이를 어떻게 구현할 수 있는지 살펴보겠습니다:

@Service
public class RateLimiter {
 //autowiring dependencies
 public Bucket resolveBucket(String key) {
 Supplier<BucketConfiguration> configSupplier = getConfigSupplierForUser(key);
 // Does not always create a new bucket, but instead returns the existing one if it exists.
 return buckets.builder().build(key, configSupplier);
 }
 private Supplier<BucketConfiguration> getConfigSupplierForUser(String key) {
 User user = userRepository.findById(userId);
 Refill refill = Refill.intervally(user.getLimit(), Duration.ofMinutes(1));
 Bandwidth limit = Bandwidth.classic(user.getLimit(), refill);
 return () -> (BucketConfiguration.builder()
 .addLimit(limit)
 .build());
 }
}

제공된 키에 대한 버킷을 반환하는 메서드를 만들었습니다. 다음 단계에서는 이를 어떻게 사용하는지 살펴보겠습니다.

토큰 소비 및 비율 제한 설정 방법

요청이 들어오면 해당 버킷에서 토큰을 소비하려고 합니다.
tryConsume()을 사용하겠습니다. 이를 수행하는 버킷 방법입니다.

@GetMapping("/user/{id}")
public String getInfo(@PathVariable("id") String id) {
 // gets the bucket for the user
 Bucket bucket = rateLimiter.resolveBucket(id);
 // tries to consume a token from the bucket
 if (bucket.tryConsume(1)) {
 return "Hello " + id;
 } else {
 return "Rate limit exceeded";
 }
}

tryConsume() 메소드가 true을 반환합니다. 토큰이 성공적으로 소비되었는지 또는 false 토큰이 소비되지 않은 경우

서비스 테스트 방법

자동화된 테스트 기술을 사용하여 이를 테스트할 수 있습니다. 예를 들어 JUnit을 사용할 수 있습니다. getInfo()을 호출하는 테스트 사례를 작성해 보겠습니다. 메소드를 여러 번 실행하여 응답이 올바른지 확인합니다.

ID가 1인 사용자가 있다고 가정해 보겠습니다. 10으로 제한됩니다. 분당 요청 수입니다. ID가 2인 사용자도 있다고 가정해 보겠습니다. 20로 제한됩니다. 분당 요청수

두 사용자 모두에 대해 11개의 요청을 적중하고 ID가 1인 사용자에 대한 요청이 실패하는지 확인합니다. 하지만 ID가 2인 사용자에 대해서는 성공합니다. .

@Test
public void testGetInfo() {
 // calls the method 10 times for user 1
 for (int i = 0; i < 10; i++) {
 rateLimiter.getInfo(1));
 rateLimiter.getInfo(2));
 }
 // verifies that the response is rate limited for user 1
 assertEquals("Rate limit exceeded", rateLimiter.getInfo(1));
 // verifies that the response is successful for user 2
 assertEquals("Hello 2", rateLimiter.getInfo(2));
}

테스트를 실행하면 테스트가 통과된 것을 확인할 수 있습니다.

결론

이 튜토리얼에서는 Spring Boot 애플리케이션에서 Bucket4j 및 Redis를 사용하여 속도 제한기를 생성하는 방법을 다루었습니다. 또한 JCache로 Redisson 클라이언트를 설정하는 방법과 이를 사용하여 버킷을 캐시하는 방법도 살펴보았습니다.

마지막으로 특정 사용자에 대한 요청 속도를 제한하는 데 사용할 수 있는 간단한 속도 제한기를 구현했습니다.

이 튜토리얼이 즐거웠기를 바랍니다. 읽어주셔서 감사합니다!

무료로 코딩을 배우세요. freeCodeCamp의 오픈 소스 커리큘럼은 40,000명 이상의 사람들이 개발자로 취업하는 데 도움을 주었습니다. 시작하세요