1. Lettuce
- setnx 명령어를 활용해 분산락을 구현할 수 있다. 이는 기존의 값이 없을 때만 set 하는 명령어입니다.
spin lock 방식으로 retry 로직을 개발자가 작성해야 합니다.
spin lock이란 lock을 획득하려는 스레드가 lock을 사용할 수 있는지 반복적으로 확인하면서 lock을 획득하는 방식
docker로 레디스를 다운로드하고 실행시키자.
docker pull redis
docker run --name myredis -d -p 6379:6379 redis
docker ps
이후 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
이후 redis cli를 사용하도록 접속
1. key가 1이고 value가 lock인 데이터를 넣는다. (성공)
2. key가 1이고 value가 lock인 데이터를 다시 넣는다 (실패)
이제 테스트를 진행한다.
public class RedisLockRepository {
private 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(3_000));
}
public Boolean unlock(Long key) {
return redisTemplate.delete(generateKey(key));
}
private String generateKey(Long key) {
return key.toString();
}
}
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)) {
Thread.sleep(100); //lock 획득 실패시 redis의 부하를 줄여준다.
}
try {
stockService.decrease(key, quantity);
} finally {
redisLockRepository.unlock(key);
}
}
}
2. Redisson
-pub-sub 기반의 Lock 구현
pub-sub은 채널을 만들고 락을 점유하고 있는 스레드가 락 획득하려고 대기 중인 스레드에게 해제를 알려주고 알림을 받은 스레드가 락을 획득하는 방법.
의존성 추가.
https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter/3.19.0
implementation 'org.redisson:redisson-spring-boot-starter:3.19.0'
pub-sub 실습
하나의 터미널에서 채널 구독
다른 터미널에서 값을 발행합니다.
그러면 채널을 통해 구독하는 터미널에 다음과 같이 출력됩니다.
즉 Redisson을 통해서 락을 획득하려고 하는 스레드들에게 lock 해제를 알릴 수 있습니다.
기존 spin-lock과 확실히 redis의 부하를 줄일 수 있는 방법이다.
또한 Redisson의 라이브러리를 클래스 구현체를 제공하기 때문에 별도의 repository클래스를 구현하지 않아도 된다.
@Component
public class RedissonLockStockFacade {
private RedissonClient redisClient;
private StockService stockService;
public RedissonLockStockFacade(RedissonClient redisClient, StockService stockService) {
this.redisClient = redisClient;
this.stockService = stockService;
}
public void decrease(Long key, Long quantity) {
RLock lock = redisClient.getLock(key.toString());
try {
boolean available = lock.tryLock(5, 1, TimeUnit.SECONDS);
if (!available) {
System.out.println("lock 획득 실패");
return;
}
stockService.decrease(key, quantity);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
1. Lock을 해제하는 과정 중 정상적으로 Lock 이 해제가 되지 않는다면 문제가 발생할 수 있는데요. 그래서 Redisson에서는 LockExpire를 설정할 수 있도록 해줍니다. 그래서 Redison의 tryLock Method에서는 leaseTime을 설정할 수 있습니다.
2. Lock 경과시간 만료 후 Lock에 접근하게 될 수도 있습니다.
만약 A 프로세스가 Lock을 취득한 후 leaseTime을 1초로 설정했다고 해봅시다.
근데 A 프로세스의 작업이 2초가 걸리는 작업이었다면 이미 Lock 은 leaseTime 이 경과하여 도중에 해제가 되었을 테고, A 프로세스는 Lock에 대해서 Monitor 상태가 아닌데 Lock을 해제하려고 할 것입니다.
따라서 IllegalMonitorStateException 이 발생하게 됩니다.
Lettuce
- 구현이 간단하다.
- spring data redis를 이용하여 lettuce이가 기본이기 때문에 별도의 라이브러리를 사용하지 않아도 됨.
- spin lock 방식이어서 redis의 부하가 갈 수 있다.
Redisson
- 락 획득 재시도를 기본으로 제공
- pub-sub 방식 구현이기에 redis의 부하가 적다
- 별도의 라이브러리를 사용해야 한다.
- 라이브러리 사용법을 공부해야 한다.
재시도가 필요하지 않은 lock은 lettuce를 활용한다. 재시도가 필요한 경우에는 redisson을 활용한다.
Pessimistic Lock VS Optimistic Lock
충돌이 적은 경우 optimistic lock 이 빠르지만, 충돌이 많다면 pessimistic lock 이 더 빠르므로, 경우에 따라 다르다.
Facade VS Helper
Facade는 내부 로직을 캡슐화하는 디자인 패턴. 사실 우리 구현사항에서 Facade에는 락을 얻는 행위만 있으므로 다른 패턴이 더 적합할 수 있지만, 구현이 매우 쉬워서 실무에서 자주 쓰는 편이다.
MySQL VS Redis
이미 MySQL을 사용하고 있다면 별도의 비용 없이 사용가능하다. 어느 정도의 트래픽까지는 문제없이 활용이 가능하다. 하지만 Redis 보다는 성능이 좋지 않다.
만약 현재 활용 중인 Redis 가 없다면 별도의 구축비용과 인프라 관리비용이 발생한다. 하지만, MySQL 보다 성능이 좋다.
'SSR' 카테고리의 다른 글
제한된 리소스에서 살아남기 (1) | 2022.12.31 |
---|---|
데이터베이스를 이용한 동시성 이슈 해결하기 (0) | 2022.12.25 |
아파치 지시어 (0) | 2022.11.13 |
Https 적용 (0) | 2022.11.12 |
댓글