Spring Boot ‐ 동시성 이슈를 Redis로 해결하기 - dnwls16071/Backend_Study_TIL GitHub Wiki
📚 Redis 라이브러리
❗Lettuce
- 구현이 간단하다.
- Spring Data Redis를 사용하면 기본적으로 Lettuce가 기본이 되므로 별도의 라이브러리를 사용하지 않아도 된다.
- spin lock 방식이기 때문에 많은 쓰레드가 lock 획득 대기 상태라면 Redis에 부하가 갈 수 있다.
❗Redisson
- 락 획득 재시도를 기본으로 제공한다.
- pub-sub 방식으로 구현이 되어있기 때문에 Lettuce와 비교했을 때, Redis에 부하가 덜 간다.
- 별도의 라이브러리를 사용해야 한다.
- Lock을 라이브러리 차원에서 제공하기 때문에 사용법을 공부해야한다.
📚 Lettuce
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
@Component
@RequiredArgsConstructor
public class RedisLockRepository {
private final RedisTemplate<String, String> 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
@RequiredArgsConstructor
public class LettuceLockStockFacade {
private final RedisLockRepository redisLockRepository;
private final StockService stockService;
public void decrease(Long productId, Long quantity) throws InterruptedException {
// 락을 획득했는지 무한 반복
while (!redisLockRepository.lock(productId)) {
// 락을 획득하지 못한 경우 락 획득 재시도
Thread.sleep(100);
}
// 락을 획득했다면 로직 실행
try {
stockService.decrease(productId, quantity);
// 수행했다면 finally문으로 이동해 락을 해제
} finally {
redisLockRepository.unlock(productId);
}
}
}
📚 Redisson
implementation 'org.redisson:redisson-spring-boot-starter:3.22.1'
@Component
public class RedissonLockStockFacade {
private RedissonClient redissonClient;
private StockService stockService;
public RedissonLockStockFacade(RedissonClient redissonClient, StockService stockService) {
this.redissonClient = redissonClient;
this.stockService = stockService;
}
public void decrease(Long productId, Long quantity) {
RLock lock = redissonClient.getLock(productId.toString());
try {
boolean b = lock.tryLock(10, 1, TimeUnit.SECONDS);
if (!b) {
System.out.println("lock 획득 실패");
return;
}
stockService.decrease(productId, quantity);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}