[Redis] Redis Lock

greenlemonT·2025년 2월 5일

Redis

목록 보기
2/3

Redis Lock

📌 1. Redis Lock이란?

분산 환경에서 동시에 여러 프로세스가 동일한 리소스에 접근하는 문제를 방지하기 위해 사용

분산 락

  • 동시에 두 개 이상의 프로세스가 같은 자원을 수정하는 경우를 방지

Redis lock 장점

  • TTL(Time-To-Live, 자동 만료 기능):
    락을 획득한 후 프로세스가 비정상 종료되더라도 자동으로 락이 해제되도록 설정할 수 있음
  • Redis Lock은 비관적 락으로, 먼저 락을 획득한 프로세스만 리소스를 변경할 수 있음.

📌 2. Redisson이란?

Redis를 사용하여 Java에서 분산 락 및 다양한 동기화 기법을 지원하는 라이브러리

✅ Redisson의 기능

  1. 분산 락
    • RLock (Reentrant Lock): 같은 스레드가 여러 번 획득할 수 있는 락
    • RWLock (Read-Write Lock): 읽기/쓰기 락 지원
    • MultiLock: 여러 락을 동시에 관리
  2. 고급 동기화 도구
    • Semaphore: 여러 개의 스레드가 동시에 접근할 수 있도록 제한
    • CountDownLatch: 특정 개수의 작업이 완료될 때까지 대기
  3. 캐시 및 데이터 구조 지원
    • RMap, RSet, RList 등 다양한 Redis 데이터 구조 지원

📌 3. Redisson을 활용한 Redis Lock 사용법

의존성 설정

dependencies {
    implementation 'org.redisson:redisson-spring-boot-starter:3.16.3'
}

EX) 쿠폰발급시 redis lock적용

🔹 3-1. issueCoupon() - Redisson을 활용한 Redis 락 적용

@Transactional
public Coupon issueCoupon(CouponDto.IssueRequest request) {
    String quantityKey = COUPON_QUANTITY_KEY + request.getCouponPolicyId();
    String lockKey = COUPON_LOCK_KEY + request.getCouponPolicyId();
    RLock lock = redissonClient.getLock(lockKey);

RLock lock = redissonClient.getLock(lockKey);

  • Redisson을 이용해 Redis 기반의 락을 생성
  • 쿠폰 정책 ID를 기반으로 각 쿠폰 발급 과정마다 고유한 락을 생성

🔹 3-2. 락 획득 시도

boolean isLocked = lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS);
if (!isLocked) {
    throw new CouponIssueException("쿠폰 발급 요청이 많아 처리할 수 없습니다. 잠시 후 다시 시도해주세요.");
}

lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS);

  • 락을 최대 LOCK_WAIT_TIME 초 동안 대기하여 획득을 시도.
  • 락을 획득하면, 최대 LOCK_LEASE_TIME 초 동안 유지됨 (이후 자동 해제).
  • 다른 프로세스가 이미 락을 보유 중이라면 일정 시간 동안 기다린 후 실패 처리.

락이 없으면 락을 획득하여 쿠폰 발급을 진행하고, 락이 이미 걸려 있다면 예외를 던져 대기

🔹 3-3. 쿠폰 발급 로직 실행

CouponPolicy couponPolicy = couponPolicyService.getCouponPolicy(request.getCouponPolicyId());
LocalDateTime now = LocalDateTime.now();
if (now.isBefore(couponPolicy.getStartTime()) || now.isAfter(couponPolicy.getEndTime())) {
    throw new IllegalStateException("쿠폰 발급 기간이 아닙니다.");
}
  • 쿠폰 정책 정보(CouponPolicy)를 가져온 후, 쿠폰 발급 가능한 기간인지 검증.

🔹 3-4. Redis를 이용한 쿠폰 수량 관리

RAtomicLong atomicQuantity = redissonClient.getAtomicLong(quantityKey);
long remainingQuantity = atomicQuantity.decrementAndGet();

if (remainingQuantity < 0) {
    atomicQuantity.incrementAndGet();
    throw new CouponIssueException("쿠폰이 모두 소진되었습니다.");
}

✅ Redis의 RAtomicLong을 사용하여 수량을 원자적으로 감소시킴

  • atomicQuantity.decrementAndGet() → 현재 쿠폰 수량을 감소시킴
  • 쿠폰 수량이 0보다 작으면 즉시 증가시키고 예외 발생 → 쿠폰 초과 발급 방지

→ MySQL 트랜잭션을 사용하지 않고도 Redis에서 초고속으로 동시성을 관리

🔹 3-5. 쿠폰 저장 및 락 해제

return couponRepository.save(Coupon.builder()
        .couponPolicy(couponPolicy)
        .userId(UserIdInterceptor.getCurrentUserId())
        .couponCode(generateCouponCode())
        .build());
  • 쿠폰을 DB에 저장한 후, 최종적으로 Redis에서 관리되는 쿠폰 수량을 업데이트한다.
} finally {
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

lock.unlock();

  • 현재 쓰레드가 락을 소유하고 있다면 락을 해제.
  • 락을 해제하지 않으면 다른 사용자가 쿠폰을 발급하지 못하는 문제가 발생할 수 있음.
  • 예외가 발생하더라도 finally 블록에서 락을 해제하여 무조건 락이 풀리도록 보장.

0개의 댓글