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] 할인 쿠폰 분산락 적용 #140

Merged

Conversation

june-777
Copy link
Member

💡 다음 이슈를 해결했어요.

Issue Link - #132



💡 이슈를 처리하면서 추가된 코드가 있어요.

AOP 기반 Redisson 분산락 적용

package camp.woowak.lab.infra.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class DistributedLockAop {
private static final String REDISSON_LOCK_PREFIX = "LOCK:";
private final RedissonClient redissonClient;
private final AopForTransaction aopForTransaction;
@Around("@annotation(camp.woowak.lab.infra.aop.DistributedLock)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
String key = REDISSON_LOCK_PREFIX + CustomSpringELParser.getDynamicValue(signature.getParameterNames(),
joinPoint.getArgs(),
distributedLock.key());
RLock rLock = redissonClient.getLock(key);
try {
boolean locked = rLock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(),
distributedLock.timeUnit());
if (!locked) {
log.warn("Failed to acquire lock for method {} with key {}", method.getName(), key);
throw new IllegalStateException("Unable to acquire lock");
}
log.info("Acquired lock for method {} with key {}", method.getName(), key);
return aopForTransaction.proceed(joinPoint);
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
log.info("Released lock for method {} with key {}", method.getName(), key);
}
}
}
}


AOP 단위로 트랜잭션 시작 로직 적용

분산락 해제 후 트랜잭션을 종료해야 하므로, AOP 단위로 트랜잭션을 시작함

package camp.woowak.lab.infra.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class AopForTransaction {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
}


CustomSpringELParser

AOP를 적용한 메서드 시그니쳐의 정보를 동적으로 파싱하기 위함

package camp.woowak.lab.infra.aop;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class CustomSpringELParser {
private CustomSpringELParser() {
}
public static Object getDynamicValue(String[] parameterNames, Object[] args, String key) {
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
for (int parmeterIdx = 0; parmeterIdx < parameterNames.length; parmeterIdx++) {
context.setVariable(parameterNames[parmeterIdx], args[parmeterIdx]);
}
return spelExpressionParser.parseExpression(key).getValue(context, Object.class);
}
}



💡 이런 고민을 했어요.

  • Redisson vs Lettuce 어떤 Redis Client가 적합할까?
  • 주문 재고 동시성에 분산락을 적용할 때, AOP 기반은 제약이 있는 것 같다.
  • AOP 기반으로 적용한 분산락의 기능을 테스트 코드로 기능 명세의 느낌으로 문서화하면 좋겠다.
  • 분산락을 적용 및 성능테스트 했을 때, 비관락보다 좋은 점이 무엇인지 고민
    • 이점: DB Connection Pool을 적게 사용
    • Response Time 관점에서는 수치적으로 비슷하다



✅ 셀프 체크리스트

  • 내 코드를 스스로 검토했습니다.
  • 필요한 테스트를 추가했습니다.
  • 모든 테스트를 통과합니다.
  • 브랜치 전략에 맞는 브랜치에 PR을 올리고 있습니다.
  • 커밋 메세지를 컨벤션에 맞추었습니다.
  • wiki를 수정했습니다.

june-777 and others added 17 commits August 22, 2024 21:45
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
- DistributedLock 애노테이션이 붙은 메서드의 파라미터를 동적으로 매핑하기 위함

Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
- 분산락을 적용한 할인 쿠폰 발급 메서드에서 사용하기 위함

Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
- 락 대기 시간 초과 시 예외 발생 로직 추가
- 락이 안 걸려 있으면 락 해제 시도 안하도록 수정

Co-authored-by: june-777 <wlwhswnsrl96@gmail.com>
- rLock.tryLock: InterruptedException
- rLock.unlock: IllegalMonitorStateException

Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
Co-authored-by: kimhyun5u <22kimhyun5u@gmail.com>
@june-777 june-777 added the 💪 Improve 기능 고도화 & 개선 label Aug 24, 2024
@june-777 june-777 added this to the 1차 고도화 milestone Aug 24, 2024
…kimhyun5u_할인쿠폰-분산락-적용

# Conflicts:
#	build.gradle
#	src/test/java/camp/woowak/lab/container/ContainerSettingTest.java
Copy link
Contributor

@Hyeon-Uk Hyeon-Uk left a comment

Choose a reason for hiding this comment

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

aop가 정말로 잘 작동되는지 통합테스트를 추가해서 확인하시면 더 안심하고 개발할 수 있습니다! 👍

@june-777 june-777 merged commit 6f2b4fe into main Aug 28, 2024
1 check passed
@kimhyun5u kimhyun5u deleted the feature/132_june-777-kimhyun5u_할인쿠폰-분산락-적용 branch September 19, 2024 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💪 Improve 기능 고도화 & 개선
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

4 participants