레디스 스핀락을 적용해서 동시성 제어를 진행한다.
public <T> T runOnLock(Long key, Supplier<T> task) {
while (true) {
if (!lock(key)) {
try {
log.info("락 획득 실패");
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new CustomException(ErrorType.FAILED_TO_ACQUIRE_LOCK);
}
} else {
log.info("락 획득 성공, lock number : {}", key);
break;
}
}
try {
return task.get();
} finally {
// Lock 해제
log.info("락 해제");
unlock(key);
}
}
스핀락을 통해 동시성 제어를 시도했으나, 실패.
10대 주차 가능한 주차장에 20개의 스레드가 동시에 입차를 요청시 15~19대의 차량이 입차에 성공
CPU과부하로 인한 redis 성능 저하로 동시성 처리 오류 → 20개의 스레드밖에 안보내서 아닐 것 같음
public <T> T runOnLock(Long key, Supplier<T> task) {
while (true) {
if (!lock(key)) {
try {
log.info("락 획득 실패");
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new CustomException(ErrorType.FAILED_TO_ACQUIRE_LOCK);
}
} else {
log.info("락 획득 성공, lock number : {}", key);
break;
}
}
try {
return task.get();
} finally {
// Lock 해제
unlock(key);
}
}
public CarInResponseDto enter(CarNumRequestDto requestDto, Admin admin) {
return redisLockRepository.runOnLock(
requestDto.getParkId(),
() -> enterLogic(requestDto, admin)
);
}
추가 문제
위와같이 코드를 작성시 enter
내부 메서드 enterLogic
을 실행할때 Transaction이 실행되지 않고 무시된다. 그 이유는 @Transactional
은 Spring의 AOP를 이용하여 동작하는데 AOP는 프록시를 이용하여 메서드를 호출하는데, enter
메서드에서 enterLogic
메서드를 호출할 때는 인스턴스가 새로 생성되지 않기 때문에 프록시가 생성되지 않고 @Transactional
이 동작하지 않는다.
따라서 enterLogic
메서드를 호출할때 별도의 트랜잭션 핸들러 클래스를 만들어 프록시 호출을 가능하게 만들어서 @Transactional
이 정상 작동 할 수 있도록 해주었다.
public CarInResponseDto enter(CarNumRequestDto requestDto, Admin user) {
if (requestDto.getParkId() == null) {
throw new CustomException(ErrorType.CONTENT_IS_NULL);
}
return redisLockRepository.runOnLock(
requestDto.getParkId(),
()->transactionHandler.runOnWriteTransaction(() -> enterLogic(requestDto, user)));
}