본문 바로가기
SideProject

구매 서비스에서 재고 동시성 이슈 해결해보기(2) - Redisson

by 배준오 2024. 2. 28.
반응형

( 이전글과 이어지는 포스트입니다. )

 

구매 서비스에서 재고 동시성 이슈 해결해보기(1) - JPA LOCK

4주 간 예약 구매 사이드 프로젝트를 진행했습니다. `예약 구매` 서비스는 특정 기념일이나 타임세일과 같이 해당 기간 또는 시간에만 상품을 사용자들에게 노출하고, 판매하는 이미 많은 브랜

devsting.tistory.com

'분산락'은 다중 서버 + 분산 DB 환경에서 동시성을 제어하기 위해 사용하는 Lock 기법입니다. 대표적으로 Mysql User Lock, Redis Lettuce, Redis Redisson이 있습니다. 반드시 Redisson을 사용할 필요는 없지만, 기본적으로 디스크를 사용하는 데이터베이스보다 메모리를 사용하는 Redis가 더 빠르게 락을 획득 및 해제할 수 있다는 점, Spin Lock을 사용하는 Lettuce보다 Pub/Sub으로 데이터 베이스 부하를 줄일 수 있다는 점에서 Redisson을 사용해보게 되었습니다.

 

개발 방법

Redisson 의존성 설정
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.redisson:redisson-spring-boot-starter:3.23.3'
코드 구현

락 범위가 트랜잭션 범위보다 크도록 @Transactional 어노테이션이 붙은 메서드를 감싸줍니다. 

@Transactional
public void purchase(Long productId, Integer quantity) {
    Stock stock = stockRepository.findByProductId(productId)
            .orElseThrow(IllegalArgumentException::new);
    stock.purchase(quantity);
}
    
public void purchase(Long productId, Integer quantity) {
        RLock lock = redissonClient.getLock(String.format("purchase:product:%d", productId));
        try {
            boolean available = lock.tryLock(40, 1, TimeUnit.SECONDS);
            if (!available) {
                System.out.println("redisson getLock timeout");
                throw new IllegalArgumentException();
            }
            stockService.purchase(productId, quantity);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
테스트 코드 구현
@Test
void 동시에_100명이_티켓을_구매한다() throws InterruptedException {
    Long productId = productRepository.save(Product.of("티켓", 36_000, "공연 티켓", ProductType.NOT_RESERVATION, Stock.of(100)))
            .getId();
    ExecutorService executorService = Executors.newFixedThreadPool(100);
    CountDownLatch countDownLatch = new CountDownLatch(100);

    for (int i = 0; i < 100; i++) {
        executorService.submit(() -> {
            try {
                stockService.purchase(productId, 1);
            } finally {
                countDownLatch.countDown();
            }
        });
    }

    countDownLatch.await();
    Stock actual = stockRepository.findByProductId(productId)
            .orElseThrow();

    assertThat(actual.getStock().getRemain()).isZero();
}

 

TODO

동시성 이슈를 처리하기 위해 연산량이 많은 재고 사용량 DB자체를 Redis로 두고 전체 재고량은 RDB에서 관리하는 방법도 있습니다. 그러면 재고 사용량 데이터를 RDB에 어떻게, 잘 싱크할지가 중요한데 Redis를 더 학습하고 이 방법을 적용해 보도록 하겠습니다. 감사합니다.

 

선물하기 시스템의 상품 재고는 어떻게 관리되어질까? | 우아한형제들 기술블로그

{{item.name}} 안녕하세요. 저는 주문서비스팀의 서버개발자 강홍구입니다. 이 글에서는 배달의민족 선물하기 서비스의 상품권 재고관리를 위한 시스템 설계에 대한 경험을 공유드리고자 합니다.

techblog.woowahan.com

Redisson 참고 자료

 

풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson

어노테이션 기반으로 분산락을 사용하는 방법에 대해 소개합니다.

helloworld.kurly.com

반응형