Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Redis를 이용한 Rate Limiter 적용 (#34) #38

Merged
merged 3 commits into from
Aug 28, 2024

Conversation

tthisag246
Copy link
Contributor

#️⃣ 연관 이슈

#34

📝 작업 내용

AI 이미지 생성 요청 API에 Redis를 이용하여 구현한 Rate Limiter를 적용하였습니다.

참고 이미지 및 자료

💬 리뷰 요구사항

Rate Limiter의 규칙에 대한 논의가 필요합니다.

우선 key는 (요청 IP와 API 엔드포인트)로 적용하였고,
제어 정책은 일반적인 API에 적용하는 GENERAL과 서버에 큰 부담을 주는 API에 적용하는 HEAVY로 구분지었습니다.

  • 현재 HEAVY에는 /api/photo-request만 포함되어 있는데, 다른 API 중 HEAVY 정책을 적용하는 게 좋을 것 같은 API가 있다면 의견 부탁드립니다!
  • 현재는 IP를 기준으로 횟수를 제한하도록 구현하였는데, IP와 사용자 계정 중 더 적절한 기준은 어떤 것일지 의견 부탁드립니다!
  • GENERAL과 HEAVY의 제한 횟수는 서버와 API의 성능을 고려해서 적절하게 조정해야 할 것 같습니다. 현재는 임의값을 설정해두었습니다.

@tthisag246 tthisag246 added the feat 기능 구현 label Aug 16, 2024
@tthisag246 tthisag246 self-assigned this Aug 16, 2024
@tthisag246 tthisag246 linked an issue Aug 18, 2024 that may be closed by this pull request
3 tasks
private final RedisClient redisClient;

@Bean
public LettuceBasedProxyManager<String> proxyManager() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

버킷 생성 및 관리

@@ -33,4 +40,9 @@ public RedisTemplate<String, Long> redisTemplate(RedisConnectionFactory connecti
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}

@Bean
public RedisClient redisClient() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LettuceBasedProxyManager 객체 생성에 필요


@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(puangUserArgumentResolver);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interceptor 등록


@Getter
@RequiredArgsConstructor
public enum RateLimitPolicy {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rate Limit 정책

private final String BLOCKED_PREFIX = "rate-limiter-blocked:";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

버킷의 key 생성

String servletPath = request.getServletPath();
String key = kakaoId + servletPath;

if (isBlocked(key)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

차단된 상태인지 확인

}

// key에 해당하는 bucket 로드. 없으면 생성
Bucket bucket = proxyManager.getProxy(LIMITER_PREFIX + key, () -> getRateLimitPolicy(servletPath));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

버킷 생성 또는 로드

// key에 해당하는 bucket 로드. 없으면 생성
Bucket bucket = proxyManager.getProxy(LIMITER_PREFIX + key, () -> getRateLimitPolicy(servletPath));

if (!bucket.tryConsume(1)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

토큰 소비 시도
토큰 소진 시 사용자 차단

Copy link
Contributor

@win-luck win-luck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기능 대비 쉽지 않은 부분이 많았고 또 제가 급박하게 드린 task였는데 잘 소화해주셔서 감사드립니다. 남은 시간 동안 Authentication & Authorization에 대한 단위 테스트를 작성해보셔도 좋을 것 같네요!

Comment on lines +43 to +53
public abstract Bandwidth getLimit();

private final String planName;

public static Bandwidth resolvePlan(final String targetPlan) {
return Arrays.stream(RateLimitPolicy.values())
.filter(policy -> policy.getPlanName().equals(targetPlan))
.map(RateLimitPolicy::getLimit)
.findFirst()
.orElseThrow(() -> new RateLimiterException(ResponseCode.RATE_LIMITER_POLICY_ERROR));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변수는 맨 위로 옮기는 게 좋을 것 같아요~!

Comment on lines +57 to +73
private BucketConfiguration getRateLimitPolicy(String contextPath) {
switch (contextPath) {
case "/api/photo-request":
return bucketConfiguration("heavy");

default:
return bucketConfiguration("general");
}
}

private void blockClient(String key) {
redisTemplate.opsForValue().set(BLOCKED_PREFIX + key, 0L, Duration.ofMinutes(5));
}

private boolean isBlocked(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(BLOCKED_PREFIX + key));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 따로 코드에 메서드명을 설정해서 구체적인 의미를 표현하는 것 좋습니다~!

@win-luck win-luck merged commit 2630a97 into GDSC-CAU:main Aug 28, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat 기능 구현
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[feat] Redis를 이용한 Rate Limiter 적용 (#34)
2 participants