본문 바로가기
트러블슈팅

동시성 제어

by 우 석 2024. 5. 8.

[문제 상황 & 원인]

  • 관리자가 상품 정보를 수정하는 동안, 사용자로부터의 구매 요청이 동시에 발생하는 과정에서 
    구매 로직이 성공적으로 처리되었음에도, 상품 수량의 동기화 문제가 발생하였습니다.

 

[해결 과정]

  • Redis 분산락 사용
  • 관리자가 상품 정보를 수정하는 과정에서 해당 상품에 대한 구매 요청을 일시적으로 대기 상태로 전환하고, 
  • 상품 정보의 변경 작업이 완료된 후에 구매 요청을 처리할 수 있도록 구현하였습니다.

 

Redis의 Redisson 라이브러리를 사용하여 pub/sub 방식의 분산락 구현

// DistributedLockAop.java
@Aspect
@Component
@Slf4j(topic = "DistributedLock 설정")
@AllArgsConstructor
public class DistributedLockAop {

    private final RedissonClient redissonClient;
    private final AopForTransaction aopForTransaction;

    @Around("@annotation(com.popcorntalk.global.annotation.DistributedLock)")
    public Object lock(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);

        String baseKey = distributedLock.lockName();
        String dynamicKey = generateDynamicKey(signature.getParameterNames(), joinPoint.getArgs(),
            distributedLock.identifier());
        String key = baseKey + " : " + dynamicKey;
        RLock lock = redissonClient.getFairLock(key);

        log.info("{} - 락 획득 시도", key);
        try {
            boolean lockAcquired = lock.tryLock(distributedLock.waitTime(),
                distributedLock.leaseTime(), distributedLock.timeUnit());
            if (!lockAcquired) {
                log.info("{} - 락 획득 실패", key);
                throw new IllegalArgumentException(key + " - RLock 획득 실패");
            }

            log.info("{} - 락 획득 성공", key);
            return aopForTransaction.proceed(joinPoint);
        } finally {
            try {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
                log.info("{} - 락 반납", key);
            } catch (IllegalMonitorStateException e) {
                log.info(e + baseKey + dynamicKey);
            }
        }
    }

 

  • 상품의 수정, 삭제, 구매 중 다른 유저가 상품에 접근할 수 없도록 상품의 ID로 분산락 적용
  • 설정 중 코드의 중복이 발생하여서 커스텀 어노테이션 생성하여 코드의 중복 최소화
  // ExchangeServiceImpl.java, 상품 구매 로직 메서드
  @Override
  @DistributedLock(lockName = "product", identifier = "productId", waitTime = 60, leaseTime = 4)
  public void createExchange(Long userId, Long productId) {

      Product product = productService.getProduct(productId);

      pointService.checkUserPoint(userId, product.getPrice());

      productAmount(product);

      pointService.deductPointForPurchase(userId, product.getPrice());

      Exchange exchange = Exchange.createOf(userId, product.getId(), product.getVoucherImage());
      exchangeRepository.save(exchange);

      notificationService.notifyPurchase(userId, ADMIN_EMAIL, product.getVoucherImage());
  }

'트러블슈팅' 카테고리의 다른 글

Feign Client 예외 처리 문제  (0) 2025.01.22
토큰 검증 오류  (0) 2024.05.08
스케쥴링  (0) 2024.04.25
배포전 로컬 테스트 중 데이터베이스 연결 문제  (0) 2024.04.19
RedisConfig의 복수의 CacheManager 문제  (0) 2024.04.15