
이번 글에서는 최상용님의 "재고시스템으로 알아보는 동시성이슈 해결방법"을 정리해 보려고 합니다.
Lettuce는 분산락을 구현할 때 사용하는 대표적인 라이브러리입니다.
SETNX를 활용해 분산락을 구현합니다.

SETNX=set if not exists의 줄임말으로,
키가 존재하지 않을 경우 값을 지정하는 방식으로 동작합니다.
또한, 스레드가 락을 사용할 수 있는지 반복적으로 확인하면서 락 획득을 시도하는
spin lock 방식을 사용합니다.
이때 retry 로직은 개발자가 작성해줘야 합니다.
Redis-CLI에 접속해서 데이터가 없는 상태에서
setnx 1 lock 명령어를 실행하면

키가 1인 데이터가 없기 때문에 성공합니다.
다시 setnx 1 lock 명령어를 실행하면?

키가 1인 데이터가 존재하기 때문에 실패하게 됩니다.
@Component
public class RedisLockRepository {
private final RedisTemplate<String, String> redisTemplate;
public RedisLockRepository(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public Boolean lock(Long key) {
return redisTemplate
.opsForValue()
.setIfAbsent(generateKey(key), "lock", Duration.ofMillis(3000));
}
public Boolean unlock(Long key) {
return redisTemplate.delete(generateKey(key));
}
private String generateKey(Long key) {
return key.toString();
}
}
@Component
public class LettuceLockStockFacade {
private final RedisLockRepository redisLockRepository;
private final StockService stockService;
public LettuceLockStockFacade(RedisLockRepository redisLockRepository, StockService stockService) {
this.redisLockRepository = redisLockRepository;
this.stockService = stockService;
}
public void decrease(Long id, Long quantity) throws InterruptedException {
while (!redisLockRepository.lock(id)) {
/**
* Lock 획득에 실패하면 Thread.sleep()을 사용해서 100ms 텀을 두고, 재시도
* 레디스의 부하를 줄임
*/
Thread.sleep(100);
}
try {
stockService.decrease(id, quantity);
} finally {
redisLockRepository.unlock(id);
}
}
}
Lettuce은 Named Lock과 비슷하지만,
세션 관리를 신경쓰지 않아도 된다는 점이 다릅니다.
Lettuce 장점
spring data redis는 기본 구현체가 Lettuce이기 때문에 별도의 라이브러리를 사용하지 않아도 됩니다.Lettuce 단점
spin lock 방식때문에 동시에 많은 스레드가 lock 획득 대기 상태라면 redis에 부하가 갈 수 있습니다. 그렇기 때문에 스레드간의 락 획득 텀을 두어야 합니다.Redisson은 PUB/SUB 기반의 Lock 구현을 제공합니다.

PUB-SUB 기반은 채널을 하나 만들고 락을 점유 중인 스레드가 락을 획득하려고 대기 중인 스레드에게 해체를 알려주면 안내를 받은 스레드가 락 획득 시도를 하는 방식입니다.
그렇기 때문에 retry 로직이 필요하지 않습니다.
메시지를 주고 받기 위해 터미널을 두 개 띄워줍니다.

ch1이라는 채널을 구독합니다.

다른 터미널에서 hello라는 메시지를 ch1 채널에 보내줍니다.

ch1을 구독하고 있는 터미널에서 메시지를 받을 수 있습니다.

// Redisson을 사용하기 위해 추가
implementation 'org.redisson:redisson-spring-boot-starter:3.25.0'
Redisson 장점
Lettuce는 계속 락 획득을 시도하는 반면에 Redisson은 락 해제가 되었을 때만 시도(PUB-SUB 기반의 구현)하기 때문에 레디스의 부하를 줄여줍니다.Redisson 단점
RedissonClient를 사용해야 합니다.Lettuce : 재시도가 필요하지 않은 경우에 사용
Redisson : 재시도가 필요한 경우에 사용
MySQL
Redis 보다 성능이 좋지 않습니다.Redis
MySQL 보다 성능이 좋기 때문에 더 많은 요청을 처리할 수 있습니다.실무에서는
MySQL을 사용Redis를 도입