백엔드 관련 강의 공부/동시성 이슈 - 재고시스템
redis를 활용한 동시성 문제 해결 - 1. Lettuce
chanhee01
2024. 2. 4. 14:11
Lettuce 라이브러리는 setnx 명령어를 활용하여 분산락을 구현하는 방식이다. setnx 명령어는 key와 value를 set할 때 기존의 값이 없을 때에만 set을 하는 명령어이다. setnx를 이용하는 방식은 spin lock 방식이므로 retry 로직을 개발자가 직접 작성해야 한다.

setnx에 대해 설명하기 위해 터미널에서 도커로 docker exec -it <container_id> redis-cli를 통해 레디스에 접속해서 명령어를 입력했다. setnx <key> <value> 형태로 입력을 했는데, 처음의 setnx 1 lock은 key가 1인 값이 없기 때문에 1을 반환해준다. 그 이후에는 key가 1인 값이 존재하기 때문에 0을 반환해주고, del 1로 삭제한 다음에 setnx 1 lock을 하면 다시 1을 반환해준다.
위의 방식을 코드로 구현해서 spin lock을 구현한다.
RedisLockRepository.class
@Repository
@RequiredArgsConstructor
public class RedisLockRepository {
private final RedisTemplate<String, String> redisTemplate;
public Boolean lock(Long key) {
return redisTemplate
.opsForValue()
.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();
}
}
우선 redis를 사용해야되기 때문에 redis repository를 만들어준다. 로직 실행 전에 key와 setnx 명령어를 통해 lock을 하고, 로직이 끝나면 unlock 메서드를 통해서 lock을 해제한다.
@Component
@RequiredArgsConstructor
public class LettuceLockStockFacade {
private final RedisLockRepository redisLockRepository;
private final StockService stockService;
public void decrease(Long id, Long quantity) throws InterruptedException {
while (!redisLockRepository.lock(id)) {
Thread.sleep(100);
}
try {
stockService.decrease(id, quantity);
} finally {
redisLockRepository.unlock(id);
}
}
}
만약에 lock이 사용중이어서 setnx 명령어를 통해 lock을 하지 못한다면 Thread.sleep(100);으로 while문을 반복하고, lock을 획득한다면 decrease를, 로직이 정상적으로 수행되면 마지막에 unlock을 해주는 로직이다.