๐ก ์ต์์ฉ๋์ ์ฌ๊ณ ์์คํ ์ผ๋ก ์์๋ณด๋ ๋์์ฑ์ด์ ํด๊ฒฐ๋ฐฉ๋ฒ ๊ฐ์๋ฅผ ๋ฃ๊ณ ์ ๋ฆฌํ ๋ด์ฉ์ ๋๋ค.
๋ฐฐ๊ฒฝ
์ง๋ ๋์์ฑ์ด์ ํด๊ฒฐ๋ฐฉ๋ฒ (2/3) - ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฝ(Lock)์ผ๋ก ํด๊ฒฐํ๊ธฐ์ ์ด์ด ์ด๋ฒ์๋ ๋ ๋์ค(Redis)๋ฅผ ํ์ฉํ์ฌ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด ๋ณธ๋ค. ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ๋ ๋์ค์์๋ ๋ํ์ ์ผ๋ก Lettuce์ Redisson์ ํ์ฉํ๋ค. (๋ ๋์ค์ ๋ํ ์ค๋ช ์ ์ด๋ฒ ๊ธ์์๋ ์๋ตํ๋ค.)
๋์์ฑ์ ์ ์ดํ๊ธฐ ์ํด์ DataBase ๋๋ Redis์์ ์ ๊ณตํ๋ Lock๋ฅผ ํ์ฉํ์ฌ ๋ฐ์ดํฐ์ ๋์์ฑ ์ ๊ทผ์ ์ ์ดํ๋ ๋ฐฉ๋ฒ์ ์ด์ฉํด์ผ ํ๋ค. DataBase Lock๋ฅผ ์ด์ฉํ ๊ฒฝ์ฐ ์ถ๊ฐ์ ์ธ ์ธํ๋ผ ๊ตฌ์ฑ์์ ์์ด ๋์์ฑ์ ํด๊ฒฐํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ผ๋ Lock ํ๋์ ์ํด Waiting๋๋ DataBase Connection ์ฆ๊ฐ๋ก ์ธํด ๋ถํ๊ฐ ๋ฐ์ํ ์ ์๋ค.
Redis๋ฅผ ์ด์ฉํ์ฌ Distributed Lock๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ DataBase Connection ์ฆ๊ฐ๋ ๋ฐฉ์ง ํ ์ ์์ง๋ง Redis์ ๊ด๋ฆฌ๊ฐ ํ์ํ๋ค.
Lettuce
๋ ๋์ค์ ์๋ฐ ํด๋ผ์ด์ธํธ์ด๋ค.
Lettuce๋ก ๋ถ์ฐ ๋ฝ(Distributed lock)์ ๊ตฌํํ๋ ๊ฒฝ์ฐ setnx ๋ช ๋ น์ด๋ฅผ ํ์ฉํ ์ ์๋ค.
์ด๋ Spin lock ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋๋ฐ, ์ฃผ์ํ ์ ์ผ๋ก retry ๋ก์ง์ ๊ฐ๋ฐ์๊ฐ ์ง์ ์์ฑํด์ฃผ์ด์ผ ํ๋ค.
- setnx : set if not exists์ ์ค์๋ง
- ํน์ ํค๊ฐ ์กด์ฌํ์ง ์์ ๋๋ง ๊ฐ์ ์ค์ ํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
- setnx๋ atomic(์์์ ) ์ฐ์ฐ์ผ๋ก ๋์ํ์ฌ, ์ฌ๋ฌ ํด๋ผ์ด์ธํธ๊ฐ ๋์์ ์ด ๋ช ๋ น์ด๋ฅผ ํธ์ถํด๋ ํ ๋ฒ์ ํ๋์ ํด๋ผ์ด์ธํธ๋ง ์ฑ๊ณตํ ์ ์๋ค.
Spin Lock
๋ฝ์ ํ๋ํ๋ ค๋ ์ค๋ ๋๊ฐ ๋ฝ์ ์ฌ์ฉํ ์ ์๋์ง ๋ฐ๋ณต์ ์ผ๋ก ํ์ธํ๋ฉด์ ๋ฝ ํ๋ค์ ์๋ํ๋ ๋ฐฉ์
Spin Lock์ผ๋ก ๋ฝ์ ํ๋ํ๋ ๊ณผ์ ์ ๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
๋จผ์ Thread-1์ด setnx ๋ช ๋ น์ด๋ก key = 1, value = lock์ธ ๋ฐ์ดํฐ๋ฅผ ๋ ๋์ค์ set ํ๋ค.
ํ์ฌ ๋ ๋์ค์๋ key๊ฐ 1์ธ ๊ฐ์ด ์์ผ๋ฏ๋ก ์ ์์ ์ผ๋ก set ํ๊ณ ์ฑ๊ณต์ ๋ฐํํ๋ค.
์ดํ Thread-2๊ฐ ๋์ผํ key์ value๋ก set์ ์๋ํ๋ค.
ํ์ง๋ง ์ด๋๋ ์ด๋ฏธ {1: lock}์ผ๋ก ๊ฐ์ด ํฌํจ๋์ด ์์ผ๋ฏ๋ก Thread-2์ set์ ์คํจํ๋ค.
Thread-2๋ Lock ํ๋์ ์คํจํ์์ผ๋ฏ๋ก ์ผ์ ์๊ฐ(e.g. 100ms) ์ดํ Lock ํ๋์ ์ฌ์๋ํ๋ค.
Redisson
Lettuce์ ๊ฐ์ ์๋ฐ ๋ ๋์ค ํด๋ผ์ด์ธํธ์ด๋ค.
Redisson์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ pub-sub ๊ธฐ๋ฐ์ผ๋ก Lock์ ๊ตฌํํ๋ค.
pub-sub
Lock์ ๊ด๋ฆฌํ๊ธฐ ์ํ ์ฑ๋์ ์์ฑํ๋ค.
Lock์ ์ ์ ์ค์ธ ์ค๋ ๋๊ฐ ์์ ์ด ๋๋๋ฉด Lock์ ํ๋ํ๋ ค๊ณ ๋๊ธฐ ์ค์ธ ์ค๋ ๋์๊ฒ ํด์ ๋ฅผ ์๋ ค์ค๋ค.
์ดํ ์๋ด๋ฅผ ๋ฐ์ ์ค๋ ๋๊ฐ Lock์ ํ๋์ ์๋ํ๋ ๋ฐฉ์์ด๋ค.
Lettuce์ ๋ฌ๋ฆฌ ๋ณ๋์ Retry ๋ก์ง์ ์์ฑํ์ง ์์๋ ๋๋ค.
Thread-1์ด Lock์ ์ ์ ํ๊ณ ์๊ณ Thread-2๊ฐ Lock ํ๋์ ์ํด ๋๊ธฐํ๊ณ ์๋ค๊ณ ๊ฐ์ ํ๋ค.
Thread-1์ด ์์ ์ด ๋๋๋ฉด Channel๋ก ์์ ์ด ๋๋ฌ๋ค๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ธ๋ค.
Channel์ ๋๊ธฐ ์ค์ด๋ Thread-2์๊ฒ Lock ํ๋์ ์๋ํ๋ผ๊ณ ๋ฉ์์ง๋ฅผ ๋ณด๋ธ๋ค.
Thread-2๋ Channel๋ก๋ถํฐ ๋ฉ์์ง๋ฅผ ๋ฐ๊ณ Lock ํ๋์ ์๋ํ๋ค.
๋ ๋์ค๋ก ๋ถ์ฐ ๋ฝ์ ๊ตฌํํ๋ ๋ํ์ ์ธ 2๊ฐ์ง ๋ฐฉ๋ฒ์ ์ด์ ๊ฐ๋ค. ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ๋ ๋์ค๋ก ๋ฝ์ ๊ตฌํํด ๋ณด์
Lettuce๋ฅผ ํ์ฉํ์ฌ ์ฌ๊ณ ๊ฐ์ ๋ก์ง ์์ฑํ๊ธฐ
Lettuce๋ฅผ ํ์ฉํ์ฌ ์ฌ๊ณ ๊ฐ์ ๋ก์ง์ ์์ฑํด ๋ณธ๋ค.
Lettuce๋ฅผ ํ์ฉํ์ฌ Lock์ ๊ด๋ฆฌํ๋ ๋ฐฉ์์ ์์ ๋์์ฑ์ด์ ํด๊ฒฐ๋ฐฉ๋ฒ (2/3) - ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฝ(Lock)์ผ๋ก ํด๊ฒฐํ๊ธฐ์์ ์๊ฐํ Mysql์ Named Lock๊ณผ ๊ฑฐ์ ์ ์ฌํ๋ค. ํ์ง๋ง Mysql์ด ์๋ Redis๋ฅผ ํ์ฉํ๋ฏ๋ก ๋ณ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์ ์ ๊ด๋ฆฌํด ์ค ํ์๋ ์๋ค๋ ์ ์ด ์๋ค.
build.gradle
์คํ๋ง ๋ถํธ ํ๋ก์ ํธ์์ ๋ ๋์ค๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด spring-boot-starter-data-redis ์์กด์ฑ์ ์ถ๊ฐํด ์ค๋ค.
Spring Data Redis๋ Lettuce๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ์ฌ ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์๋ ๋๋ค.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
...
}
gradle์ reload ํ๊ณ ๋๋ฉด ์ข์ธก External Libraries ๋ชฉ๋ก์์ spring-boot-starter-data-redis ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ถ๊ฐ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
Redis CLI ์คํ
docker exec -it 8ce13a11909e redis-cli ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ์ฌ redis์๋ฒ์ ์ ์ํด ๋ณด์.
์๋์ ๊ฐ์ด ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ์ฌ ๊ฐ๋จํ๊ฒ setnx์ ๋ํด ์์๋ณด์.
์ฒ์ setnx 1 lock์ ์ ๋ ฅํ์ฌ ํค๊ฐ 1์ธ ๋ฐ์ดํฐ๋ฅผ ์ค์ ํ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ค์ setnx 1 lock์ ์ ๋ ฅํ๋ฉด ์ด๋ฏธ ํค๊ฐ 1์ธ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํด ์คํจํ๋ค.
๊ทธ๋์ del 1์ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ setnx 1 lock์ ์ ๋ ฅํ๋ฉด ์ฑ๊ณตํ๋ค.
RedisLockRepository
๋ ๋์ค์ ์ ๊ทผํ์ฌ ๋ฐ์ดํฐ์ CRUD ํ๊ธฐ ์ํ ํด๋์ค์ด๋ค.
Lock ํ๋๊ณผ ํด์ ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ค.
redisTemplate์ setIfAbsent ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋ ๋์ค์ setnx ๋ช ๋ น์ด๋ฅผ ํธ์ถํ๋ค.
์ด๋ ํ์์์์ 3000ms๋ก ์ง์ ํ์๋ค.
@Component
public class RedisLockRepository {
private RedisTemplate<String, String> redisTemplate;
public RedisLockRepository(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// Lock ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ค
public Boolean lock(Long key) {
return redisTemplate
.opsForValue()
// setnx ๋ช
๋ น์ด
.setIfAbsent(generateKey(key), "lock", Duration.ofMillis(3_000));
}
public Boolean unlock(Long key) {
return redisTemplate.delete(generateKey(key));
}
private String generateKey(Long key) {
return key.toString();
}
}
LettuceLockStockFacade
๋น์ฆ๋์ค ๋ก์ง ์ํ ์ ํ๋ก ๋ ๋์ค์ Lock์ ์ถ๊ฐ, ํด์ ํ๊ธฐ ์ํ ํด๋์ค์ด๋ค.
Lock ํ๋์ ์คํจํ๋ค๋ฉด ๋ค์ ๋ ๋์ค์ ๋ฝ์ ํ๋ํ ์ ์๋์ง ์ฌ์๋ํ๋ค.(Spin Lock ๋ฐฉ์)
์ด๋ 100ms ํ ์ ๋์ด ๋ ๋์ค์ ๋ถํ๋ฅผ ์ค์ธ๋ค.
@Slf4j
@Component
public class LettuceLockStockFacade {
private RedisLockRepository redisLockRepository;
private StockService stockService;
public LettuceLockStockFacade(RedisLockRepository redisLockRepository, StockService stockService) {
this.redisLockRepository = redisLockRepository;
this.stockService = stockService;
}
public void decrease(Long key, Long quantity) throws InterruptedException {
while (!redisLockRepository.lock(key)) {
log.info("lock ํ๋ ์คํจ");
Thread.sleep(100);
}
// Lock ํ๋์ ์ฑ๊ณตํ๋ค๋ฉด ์ฌ๊ณ ๊ฐ์ ๋ก์ง ์คํ
log.info("lock ํ๋");
try {
stockService.decrease(key, quantity);
} finally {
// ๋ก์ง์ด ๋ชจ๋ ์ํ๋์๋ค๋ฉด Lock ํด์
redisLockRepository.unlock(key);
log.info("lock ํด์ ");
}
}
}
LettuceLockStockFacadeTest
LettuceLockStockFacade ํด๋์ค์ decrease() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ์ฑ๊ณต์ ์ผ๋ก ๋์์ฑ ์ด์๋ฅผ ํด๊ฒฐํ๋์ง ํ ์คํธํ๋ค. ์์กด๊ด๊ณ๋ง LettuceLockStockFacade๋ก ๋ณ๊ฒฝํ์ฌ ๊ธฐ์กด์ ํ ์คํธ ์ฝ๋๋ฅผ ์ฌํ์ฉํด๋ ๋๋ค.
@SpringBootTest
class LettuceLockStockFacadeTest {
@Autowired
private LettuceLockStockFacade lettuceLockStockFacade;
@Autowired
private StockRepository stockRepository;
// ๊ฐ ํ
์คํธ๊ฐ ์คํ๋๊ธฐ ์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ
์คํธ ๋ฐ์ดํฐ ์์ฑ
@BeforeEach
public void before() {
stockRepository.saveAndFlush(new Stock(1L, 100L));
}
// ๊ฐ ํ
์คํธ๋ฅผ ์คํํ ํ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ
์คํธ ๋ฐ์ดํฐ ์ญ์
@AfterEach
public void after() {
stockRepository.deleteAll();
}
@Test
public void ๋์์_100๊ฐ์_์์ฒญ() throws InterruptedException {
// when
// 100๊ฐ์ ์ฐ๋ ๋ ์ฌ์ฉ(๋ฉํฐ์ค๋ ๋)
int threadCount = 100;
// ExecutorService : ๋น๋๊ธฐ๋ก ์คํํ๋ ์์
์ ๊ฐ๋จํ๊ฒ ์คํํ ์ ์๋๋ก ์๋ฐ์์ ์ ๊ณตํ๋ API
ExecutorService executorService = Executors.newFixedThreadPool(32);
// CountDownLatch : ์์
์ ์งํ์ค์ธ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์์
์ ์๋ฃํ ๋๊น์ง ๋๊ธฐํ ์ ์๋๋ก ๋์์ฃผ๋ ํด๋์ค
CountDownLatch latch = new CountDownLatch(threadCount);
// 100๊ฐ์ ์์
์์ฒญ
for(int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
lettuceLockStockFacade.decrease(1L, 1L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
latch.countDown();
}
});
}
latch.await();
// then
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0, stock.getQuantity());
}
}
์คํํ๋ฉด ํ ์คํธ๊ฐ ์ฑ๊ณตํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์คํ ๋ก๊ทธ๋ฅผ ํ์ธํ์ฌ ๋์ ๋ฐฉ์์ ์ถ์ ํด ๋ณด์.
์์ ์ค๋ ๋ ์ค ํ๋์ธ pool-1-thread-4 ์ค๋ ๋๊ฐ lock ํ๋์ ์ฑ๊ณตํ๋ค.
์ดํ ๋ชจ๋ ์ค๋ ๋๋ pool-1-thread-4๊ฐ Lock์ ๋ฐ๋ฉํ๊ธฐ ์ ๊น์ง ๋ฝ ํ๋์ ์คํจํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
pool-1-thread-4 ์ค๋ ๋๊ฐ ๋น์ฆ๋์ค ๋ก์ง(์ฌ๊ณ ์ฐจ๊ฐ ๋ก์ง)์ ๋ชจ๋ ์ํํ๊ณ ๋์ Lock์ ํด์ ํ๋ฉด ๋ค๋ฅธ ์ค๋ ๋(pool-1-thread-2)๊ฐ Lock์ ํ๋ํ๋ค. ์ดํ ๋ชจ๋ ์ค๋ ๋๋ pool-1-thread-2๊ฐ Lock์ ๋ฐ๋ฉํ ๊ธฐ ์ ๊น์ง ํ๋์ ์คํจํ๊ณ ๋ค์ ๋๊ธฐํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
Lettuce๋ฅผ ํ์ฉํ ๋ฐฉ์์ ์ฅ์ ์ผ๋ก๋ ๊ตฌํ์ด ๊ฐ๋จํ๊ณ , ์ฝ๊ฒ Lock์ ํ์์์์ ๊ฑธ ์ ์๋ค๋ ์ ์ด ์๋ค.
ํ์ง๋ง Spin lock ๋ฐฉ์์ด๋ฏ๋ก ๋ชจ๋ ์ค๋ ๋๊ฐ Lock์ ํ๋ํ๊ธฐ ์ํด ๊ณ์ ๋ ๋์ค์ ์์ฒญ์ ๋ณด๋ด๋ฉด์ ๋ ๋์ค ์๋ฒ์ ๋ถํ๋ฅผ ์ค ์ ์๋ค. ๋ฐ๋ผ์ ์์์ ๊ฐ์ด Thread.sleep()์ ํตํด Lock์ ์๋ํ๋ ์ค๊ฐ์ ํ ์ ๋์ด์ผ ํ๋ค.
Redisson๋ฅผ ํ์ฉํ์ฌ ์ฌ๊ณ ๊ฐ์ ๋ก์ง ์์ฑํ๊ธฐ
์ด๋ฒ์๋ Redisson๋ฅผ ํ์ฉํ์ฌ pub-sub ๊ธฐ๋ฐ์ผ๋ก Lock์ ๊ตฌํํ๋๋ฐ, ๊ทธ ๋์ ์๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
1. ํ ์ค๋ ๋๊ฐ ์์ ์ด ์ ์ ํ๊ณ ์๋ Lock์ ํด์ ํ ๋ ์์ ๊ฐ์ด Channel์ ์์ ์ด ๋๋ฌ๋ค๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ธ๋ค.
2. ์ด๋ฅผ ์์ ๋ฐ์ Channel์ ๋ฝ์ ํ๋ํ๋ ค ํ๋ ์ค๋ ๋์๊ฒ Lock์ ํ๋ํ๋ผ๊ณ ์๋ ค์ค๋ค.
3. Lock์ ํ๋ํ๋ ค ํ๋ ์ค๋ ๋๋ค์ ๋ฉ์์ง๋ฅผ ๋ฐ์ผ๋ฉด Lock ํ๋์ ์๋ํ๋ค.
Redisson์ Spin Lock์ ์ฌ์ฉํ์ฌ ๊ณ์ Lock ํ๋์ ์๋ํ๋ Lettuce์ ๋ฌ๋ฆฌ Lock ํด์ ๊ฐ ๋์์ ๋ ํ๋ฒ(ํน์ n๋ฒ๋ง) Lock ํ๋์ ์๋ํ๊ธฐ ๋๋ฌธ์ ๋ ๋์ค์ ๋ถํ๋ฅผ ์ค์ผ ์ ์๋ค.
build.gradle
Redisson์ ์คํ๋ง๋ถํธ ํ๋ก์ ํธ์ ์ฌ์ฉํ๊ธฐ ์ํด redisson-spring-boot-starter ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ์ ์ถ๊ฐํ๋ค.
Lettuce๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ Spring Data Redis์ ๋ฌ๋ฆฌ ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผํ๋ค.
๋ฐ๋ผ์ Lock์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐจ์์์ ์ ๊ณตํ๊ณ ์์ด ๋ณ๋์ ์ฌ์ฉ๋ฒ์ ์ตํ์ผ ํ๋ค. (๋ฌ๋์ปค๋ธ๊ฐ ์๋ค.)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation group: 'org.redisson', name: 'redisson-spring-boot-starter', version: '3.34.1'
...
}
Redis CLI ์คํ
์ด๋ฒ์๋ redis cli๋ฅผ ํตํด ๊ฐ๋จํ๊ฒ ๋ช ๋ น์ด๋ฅผ ์์๋ณด์.
์ฐ์ ์ด๋ฒ์๋ ๋๊ฐ์ redis cli๊ฐ ํ์ํ๋ค.
1. ๋จผ์ ํ๋์ redis cli์์ subscribe ch1 ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ์.
์ด ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ๋ฉด ch1์ด๋ผ๋ ์ฑ๋์ ๊ตฌ๋ ํ๊ฒ ๋๋ค.
2. ๊ทธ๋ฆฌ๊ณ ๋ค๋ฅธ cli์์๋ publish ch1 hello ์ ๋ ฅํ์.
์ด ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ๋ฉด ch1 ์ฑ๋์ hello๋ผ๋ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ธ๋ค.
ch1 ์ฑ๋์ hello ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ฉด ch1 ์ฑ๋์ ๊ตฌ๋ ํ๊ณ ์๋ ๊ณณ์์ hello ๋ผ๋ ๋ฉ์์ง๋ฅผ ๋ฐ๋๋ค.
์ ๋ฆฌ
๋ ๋์ค๋ ์์ ์ด ์ ์ ํ๊ณ ์๋ ๋ฝ์ ํด์ ํ ๋ ์ฑ๋์ ๋ฝ์ ํด์ ํ๋ค๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ธ๋ค.
๊ทธ๋ฌ๋ฉด ๋ฝ์ ํ๋ํ๊ธฐ ์ํด ํด๋น ์ฑ๋์ ๊ตฌ๋ ํ๊ณ ์๋ ์ค๋ ๋๋ค์ ๋ฉ์์ง๋ฅผ ๋ฐ๊ณ ๋ฝ ํ๋ค์ ์๋ํ๋ค.
์ด์ฒ๋ผ Redisson์ Lock ํด์ ๊ฐ ๋์์ ๋ ํ๋ฒ(ํน์ n๋ฒ๋ง) Lock ํ๋์ ์๋ํ๊ธฐ ๋๋ฌธ์ ๋ ๋์ค์ ๋ถํ๋ฅผ ์ค์ผ ์ ์๋ค.
RedissonLockStockFacade
Redisson์ ํ์ฉํ์ฌ Lock์ ํ๋ํ๊ณ ํด์ ํ๋ ํด๋์ค๋ฅผ ์์ฑํ๋ค.
Redisson์ ๋์์ฑ ์ ์ด๋ฅผ ์ํ Lock ๊ด๋ จ ํด๋์ค๋ฅผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐจ์์์ ์ ๊ณตํ๊ณ ์์ผ๋ฏ๋ก ๊ฐ๋ฐ์๊ฐ ๋ณ๋๋ก Repository๋ฅผ ์์ฑํ ํ์๋ ์๋ค. ํ์ง๋ง ๋น์ฆ๋์ค ๋ก์ง ์ํ ์ ํ๋ก Lock์ ํ๋ํ๊ณ ํด์ ํ๋ ๋ก์ง์ ์์ฑํด์ผํ๋ฏ๋ก Facade ํด๋์ค๋ฅผ ์์ฑํด์ค๋ค.
@Slf4j
@Component
public class RedissonLockStockFacade {
// Lock ํ๋/ํด์ ๋ฅผ ์ํ RedissonClient ์ถ๊ฐ
private final RedissonClient redissonClient;
private final StockService stockService;
public RedissonLockStockFacade(RedissonClient redissonClient, StockService stockService) {
this.redissonClient = redissonClient;
this.stockService = stockService;
}
public void decrease(Long id, Long quantity) {
// RedissonClient์ ํ์ฉํ์ฌ Lock ๊ฐ์ฒด ์กฐํ
RLock lock = redissonClient.getLock(id.toString());
try {
// Lock ํ๋ ์๋
boolean available = lock.tryLock(
10, // lock ํ๋์ ์ํ ๋๊ธฐ ์๊ฐ
5, // lock ์ ์ ์๊ฐ
TimeUnit.SECONDS
);
// Lock ํ๋์ ์คํจํ ๊ฒฝ์ฐ
if(!available) {
log.info("lock ํ๋ ์คํจ");
return;
}
// Lock ํ๋์ ์ฑ๊ณตํ ๊ฒฝ์ฐ - ๋น์ฆ๋์ค ๋ก์ง ์ํ
stockService.decrease(id, quantity);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// ๋ก์ง์ด ๋ชจ๋ ์ ์์ ์ผ๋ก ์ํ๋์๋ค๋ฉด Lock์ ํด์ ํ๋ค.
lock.unlock();
}
}
}
RedissonLockStockFacadeTest
RedissonLockStockFacade์ decrease() ๋ฉ์๋๋ฅผ ํ ์คํธํ๋ค.
์ ์ฒด์ ์ธ ๋ก์ง์ LettuceLockStockFacadeTest์ ์ ์ฌํ๋ค.
@Slf4j
@SpringBootTest
class RedissonLockStockFacadeTest {
@Autowired
private RedissonLockStockFacade redissonLockStockFacade;
@Autowired
private StockRepository stockRepository;
@BeforeEach
public void before() {
stockRepository.saveAndFlush(new Stock(1L, 32L));
}
@AfterEach
public void after() {
stockRepository.deleteAll();
}
@Test
public void ๋์์_100๊ฐ์_์์ฒญ() throws InterruptedException {
// when
// 100๊ฐ์ ์ฐ๋ ๋ ์ฌ์ฉ(๋ฉํฐ์ค๋ ๋)
int threadCount = 100;
// ExecutorService : ๋น๋๊ธฐ๋ก ์คํํ๋ ์์
์ ๊ฐ๋จํ๊ฒ ์คํํ ์ ์๋๋ก ์๋ฐ์์ ์ ๊ณตํ๋ API
ExecutorService executorService = Executors.newFixedThreadPool(100);
// CountDownLatch : ์์
์ ์งํ์ค์ธ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์์
์ ์๋ฃํ ๋๊น์ง ๋๊ธฐํ ์ ์๋๋ก ๋์์ฃผ๋ ํด๋์ค
CountDownLatch latch = new CountDownLatch(threadCount);
// 100๊ฐ์ ์์
์์ฒญ
for(int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
redissonLockStockFacade.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
// then
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0, stock.getQuantity());
}
}
ํ ์คํธ๋ฅผ ์คํํ๋ฉด ์ฑ๊ณตํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
Lock์ ํ๋ํ ์ค๋ ๋(ool-1-thread-14) ๋น์ฆ๋์ค ๋ก์ง์ ์ํํ๊ณ ๋์ ํด์ ํ๊ณ ๋๋ฉด ๋ค๋ฅธ ์ฐ๋ ๋(ool-1-thread-11)๊ฐ ๋ค์ Lock์ ํ๋ํ์ฌ ๋ก์ง์ ์ํํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
Spin lock ๋ฐฉ์์ Lettuce์ ๋ฌ๋ฆฌ pub/sub ๊ธฐ๋ฐ์ ๊ตฌํ์ผ๋ก ๋ ๋์ค์ ๋ถํ๋ฅผ ์ค์ผ ์ ์๋ค.
ํ์ง๋ง ๊ตฌํ ๋ก์ง์ด ๋ณต์กํ๊ณ ๋ณ๋์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ๊ทธ ์ฌ์ฉ๋ฒ์ ์ตํ์ผํ๋ค๋ ๋ถ๋ด์ด ์๋ค.
์ ๋ฆฌ
์ ๋ฆฌํด๋ณด๋ฉด Redis๋ก ๋ถ์ฐ ๋ฝ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ผ๋ก๋ Lettuce๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์๊ณผ Redisson์ ์ฌ์ฉํ๋ ๋ฐฉ์์ด ์๋ค.
Lettuce๋ ๊ตฌํ์ด ๊ฐ๋จํ๊ณ Spring Data Redis๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก Lettuce๋ฅผ ์ ๊ณตํ๊ณ ์์ด ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์๋ ๋๋ค๋ ์ฅ์ ์ด ์๋ค. ํ์ง๋ง Spin lock ๋ฐฉ์์ผ๋ก Lock์ ํ๋ํ๊ณ , ํ๋ํ๊ธฐ ์ํด ๋๊ธฐํ๋ฏ๋ก Redis์ ๋ถํ๋ฅผ ์ค ์ ์๋ค.
๋ฐ๋ฉด Redisson์ pub/sub ๋ชจ๋ธ๋ก Lettuce์ ๋นํด Redis์ ๋ถํ๋ฅผ ๋ ์ค๋ค.
๋ํ Lock ํ๋ ์ฌ์๋๋ฅผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐจ์์์ ์ ๊ณตํ๊ณ ์์ด ๊ฐ๋ฐ์๊ฐ ๋ณ๋๋ก ์์ฑํด์ค ํ์๊ฐ ์๋ค.
ํ์ง๋ง ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฌ์ฉ๋ฒ์ ๋ฐ๋ก ์ตํ์ผํ๋ค๋ ์ ์์ ๋ฌ๋์ปค๋ธ๊ฐ ์๋ค.
์ค๋ฌด์์๋ ์ฌ์๋๊ฐ ํ์ํ์ง ์๋ค๋ฉด Lettuce๋ฅผ, ๋ณ๋์ ์ฌ์๋ ๋ก์ง์ด ํ์ํ๋ค๋ฉด Redisson์ ํ์ฉํ ์ ์๋ค.