멀티 인스턴스 환경일 때 특정 key값에 대해서 동시성(동시 수정) 방지를 위해 사용한다.
Redis의 싱글 스레드라는 특성, Redisson 라이브러리를 이용한다.
Hash 자료구조를 사용한다.
- Key: lock:{key이름}:{보통 사용하는 id값}
- Field: {Redisson UUID} : {Java Thread ID}
소유자 식별자로 사용하며, Redisson UUID를 사용하면 RedissonClient 인스턴스가 서버(JVM) 인스턴스마다 고유하게 부여한다.
ex) a3f2e1d0-b7c4-4f2a-9e88-1234abcd5678 : 52
- Value: 재진입 횟수로 정수값으로 기록한다.
키 및 로직
HEXIST로 내가 소유한 락인지 확인한다. HINCRBY로 원자적으로 증가시킨다. Lua Script를 통해서 다음과 같이 구현한다.
- key 구성: EVALSHA <script> 2 <KEYS[1]> <KEYS[2]> <ARGV[1]> <ARGV[2]> <ARGV[3]>
- 값 치환:
KEYS[1]: 실제 key 이름
KEYS[2]: pub/sub 채널명. ex) redisson_lock__channel:{lock key 값}
ARGV[1]: 소유자. UUID:threadId
ARGV[2]: leaseTime
ARGV[3]: pub 메시지. ex) 0 -> 0을 전송하여 이 field가 Hash에서 lock이 안잡혀 있음을 알림.
-- 1. 내가 소유한 락인지 확인
if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then
return nil -- 내 락이 아니면 무시
end
-- 2. 재진입 카운트 감소
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1)
-- 3. 아직 카운트가 남아있으면 TTL만 갱신
if (counter > 0) then
redis.call('pexpire', KEYS[1], ARGV[2])
return 0
end
-- 4. 카운트가 0이면 키 삭제 + 대기 중인 스레드에 알림
redis.call('del', KEYS[1])
redis.call('publish', KEYS[2], ARGV[3])
return 1
재진입 처리(reentrant)
코드가 길어지면 로직 내부에서 키 값을 중첩으로 호출할 수 있기 때문에, 같은 로직 내에서는 재진입이 가능하도록 방어적 설계로 처리함.
항상 lock과 해제가 쌍으로 돌아가야 정상적으로 0으로 돌아가며, 내부 unlock을 빠뜨리면 leaseTime동안 다른 스레드가 대기하게 된다.
lock → count: 0 → 1 (외부 획득)
lock → count: 1 → 2 (내부 재진입)
unlock → count: 2 → 1 (내부 해제, 키 유지)
unlock → count: 1 → 0 (외부 해제, 키 삭제 → 다른 스레드 획득 가능)'Programming-[Backend] > Redis, Memcached' 카테고리의 다른 글
| [TIL] Redis Replica와 Cluster 사용 상황 정리 (0) | 2025.09.01 |
|---|---|
| Redis 개념, Local Memory 대신 사용 이유, 설치(CLI, GUI) (0) | 2022.04.14 |