왜 Redis Lock이 필요한가?
문제 상황
단순 조회 후 수정 방식에서는 두 요청이 동시에 isSelected == false 확인
동시에 true로 변경 → 중복 선택 발생
단 하나의 요청만 자원을 수정
나머지는 즉시 실패 또는 대기
Redis Lock을 선택한 이유
Redis Lock 특징
분산 환경에서도 동시성 제어 가능
DB Lock보다 빠르고 부담이 적음
서버가 여러 대여도 하나의 Redis를 통해 락 공유
DB Lock만으로 부족한 이유
트랜잭션 범위가 길어질수록 DB 부하 증가
API 서버 확장 시 제어 어려움
AOP로 Lock을 분리한 이유
lock();
try {
비즈니스 로직
} finally {
unlock();
}
가독성 ↓
실수로 unlock 누락 위험
AOP 적용 후
@RedisLock(key = ...)
public void businessLogic() { ... }
락 로직 완전 분리
비즈니스 로직에 동시성 코드 없음
선언적으로 락 적용 가능
핵심 키워드 정리
AOP 관련
@Aspect
@Around
ProceedingJoinPoint
횡단 관심사 (Cross-cutting Concern)
Redis Lock 관련
SETNX (setIfAbsent)
TTL (Time To Live)
분산 락 (Distributed Lock)
Lua Script (원자적 해제)
설계 관련
SRP (단일 책임 원칙)
DIP (의존성 역전 원칙)
선언적 프로그래밍
관심사 분리 (Separation of Concerns)
RedisLock 어노테이션 설계
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
String key();
}
METHOD 단위 → 락은 로직 실행 범위에 적용
RUNTIME 유지 → AOP 실행 시 접근 필요
key를 SpEL로 받음 → 동적 락 키 생성
SpEL을 이용한 Lock Key 생성
@RedisLock(key = "'seat:' + #request.scheduleId + ':' + #request.seatNo")
메서드 파라미터 기반으로 자원 단위 락 구현 가능
예시 결과
lock:seat:3:15
RedisLockAspect의 역할
역할 요약
어노테이션 감지
SpEL 파싱
LockService 호출
비즈니스 메서드 실행 제어
핵심 흐름
@RedisLock 감지
Lock Key 생성
Redis Lock 획득
joinPoint.proceed() 실행
finally에서 락 해제
LockService의 책임 분리
public <T> T executeWithLock(String key, Supplier<T> action)
책임
락 획득 / 해제
TTL 설정
락 실패 시 즉시 예외 처리
왜 Supplier?
비즈니스 로직을 함수로 전달
락 범위를 명확히 제어
RedisLockRepository 구현 포인트
SET key value NX PX ttl
한 요청만 성공
TTL로 데드락 방지
락 해제 (Lua Script)
if redis.call('get', key) == value then
del key
end
왜 @Around를 사용했는가?
락은 메서드 실행 전 필요
메서드 실행 후 반드시 해제
try-finally 구조 필요
이 구조의 장점 정리
요약
Redis 기반 분산 락을 AOP로 분리하여
비즈니스 로직과 동시성 제어를 분리했고,
SpEL을 활용해 자원 단위의 최소 범위 락을 구현했다!!
공부할게 산더미!!!!!!!!!!!!!!!(;´д`)ゞ
내일 정독해야지