From ab0a48f770f914006651f7023dcf411b70e39f05 Mon Sep 17 00:00:00 2001 From: haerong22 Date: Wed, 21 Dec 2022 00:50:51 +0900 Subject: [PATCH] #27 stock: race condition solution 3 - redis(redisson) --- stock/build.gradle | 3 + .../stock/facade/RedissonLockStockFacade.java | 37 +++++++++++ .../facade/RedissonLockStockFacadeTest.java | 61 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 stock/src/main/java/com/example/stock/facade/RedissonLockStockFacade.java create mode 100644 stock/src/test/java/com/example/stock/facade/RedissonLockStockFacadeTest.java diff --git a/stock/build.gradle b/stock/build.gradle index 7af3be33..f7f0d897 100644 --- a/stock/build.gradle +++ b/stock/build.gradle @@ -22,6 +22,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation 'org.redisson:redisson-spring-boot-starter:3.19.0' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/stock/src/main/java/com/example/stock/facade/RedissonLockStockFacade.java b/stock/src/main/java/com/example/stock/facade/RedissonLockStockFacade.java new file mode 100644 index 00000000..168aef20 --- /dev/null +++ b/stock/src/main/java/com/example/stock/facade/RedissonLockStockFacade.java @@ -0,0 +1,37 @@ +package com.example.stock.facade; + +import com.example.stock.service.StockService; +import lombok.RequiredArgsConstructor; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Component +@RequiredArgsConstructor +public class RedissonLockStockFacade { + + private final RedissonClient redissonClient; + + private final StockService stockService; + + public void decrease(Long id, Long quantity) { + RLock lock = redissonClient.getLock(id.toString()); + + try { + boolean available = lock.tryLock(10, 1, TimeUnit.SECONDS); + + if (!available) { + System.out.println("get lock fail."); + return; + } + + stockService.decrease(id, quantity); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + lock.unlock(); + } + } +} diff --git a/stock/src/test/java/com/example/stock/facade/RedissonLockStockFacadeTest.java b/stock/src/test/java/com/example/stock/facade/RedissonLockStockFacadeTest.java new file mode 100644 index 00000000..25bc8d53 --- /dev/null +++ b/stock/src/test/java/com/example/stock/facade/RedissonLockStockFacadeTest.java @@ -0,0 +1,61 @@ +package com.example.stock.facade; + +import com.example.stock.domain.Stock; +import com.example.stock.repository.StockRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +class RedissonLockStockFacadeTest { + @Autowired + private RedissonLockStockFacade redissonLockStockFacade; + + @Autowired + private StockRepository stockRepository; + + @BeforeEach + public void before() { + Stock stock = new Stock(1L, 100L); + + stockRepository.saveAndFlush(stock); + } + + @AfterEach + public void after() { + stockRepository.deleteAll(); + } + + @Test + public void 동시에_100개의_요청_redisson() throws InterruptedException { + int threadCount = 100; + ExecutorService executorService = Executors.newFixedThreadPool(32); + + CountDownLatch latch = new CountDownLatch(threadCount); + + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + redissonLockStockFacade.decrease(1L, 1L); + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + + Stock stock = stockRepository.findById(1L).orElseThrow(); + + // 100 - (1 * 100) = 0 + assertEquals(0L, stock.getQuantity()); + } +} \ No newline at end of file