1️⃣ Redis + Lettuce 분산 락 구현 방식 요약
Lettuce를 활용한 SETNX 기반의 분산 락 구조
간단하면서도 안정적인 구조로, Redis를 통해 멀티 인스턴스 환경에서도 예매 중복을 제어 가능
✅ 락 키 구성
- lock:seat:{seatId} 형식 (예: lock:seat:1001)
- 좌석 단위로 분산 락을 설정합니다.
✅ 락 소유자 식별
- 락 값을 UUID로 설정하여, 락 소유자를 명확히 구분합니다.
- 락 해제 시 자신이 소유한 락만 해제되도록 보장합니다.
✅ 락 만료시간
- 기본 5초(px=5000)로 설정하여 데드락을 방지합니다.
✅ 전체 구조
[BookingController]
↓
[BookingFacade] ← 분산 락 시도
↓
[LettuceDistributedLockService]
↓
[BookingService] ← 트랜잭션
- 분산 락과 트랜잭션을 명확히 분리
- 락 책임은 Facade 계층, 트랜잭션은 Service
2️⃣ 락 로직 핵심 클래스
더보기
LettuceLockManager: 단일 키 락 획득/해제
public boolean tryLock(String key, String value, long expireMillis) {
return "OK".equals(redisCommands.set(key, value, SetArgs.Builder.nx().px(expireMillis)));
}
public void unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisCommands.eval(script, ScriptOutputType.INTEGER, new String[]{key}, value);
}
LettuceMultiLock: 여러 좌석 동시 락 제어
public boolean tryLockAll() {
for (String key : keys) {
if (!lockManager.tryLock(key, lockValue, expireMillis)) {
unlockAll(); return false;
}
}
return true;
}
LettuceDistributedLockService: 전체 흐름 제어
public void executeWithLock(List<Long> seatIds, Runnable task) {
List<String> keys = seatIds.stream()
.sorted()
.map(id -> "lock:seat:" + id)
.toList();
LettuceMultiLock lock = new LettuceMultiLock(keys, 5000L, lockManager);
if (!lock.tryLockAll()) throw new BookingException(LOCK_ACQUIRE_FAIL);
try {
task.run();
} finally {
lock.unlockAll();
}
}
3️⃣ 테스트 결과 및 정리
100명 이상이 동일 좌석 A-1 예매 시도 (JMeter 사용)
| 동시 요청 수 | 성공 | 실패 | 비고 |
| 100 | 1 | 99 | 정상 작동 |
| 200 | 2~3 | 197~198 | 중복 발생 가능성 있음 |
중복 성공은 트랜잭션 시작 전 락과의 시간 차이 때문
해결 방법은 다음 포스트에서 트러블슈팅으로 정리 예정
'SPRING' 카테고리의 다른 글
| [250602 트러블 슈팅] 크롤링 도중에 IP밴 (3) | 2025.06.02 |
|---|---|
| [SPRING] Lettuce 분산 락 트러블슈팅 (1) | 2025.05.28 |
| [SPRING] 동시성 제어 분산 락 (2) | 2025.05.26 |
| [SPRING] 커스텀 메트릭 수집 및 시각화 (2) | 2025.05.20 |
| [SPRING] 메트릭 기반 모니터링 시스템(Prometheus, Grafana) (2) | 2025.05.19 |