RLock이 Redis 위에서 분산락을 구현하는 방식 정리RLock)key = 락 이름, field = 소유자(UUID:threadId), value = 재진입 횟수-- KEYS[1]=락 이름, ARGV[1]=TTL(ms), ARGV[2]=소유자(UUID:threadId)
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[2], 1); -- 신규 획득, 카운트 1
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1); -- 재진입, 카운트 +1
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]); -- 남이 점유 중 → 남은 TTL 반환
Semaphore(0).tryAcquire(ttl)로 블록publish되면 → 대기 스레드가 깨어나 재시도-- KEYS[1]=락 이름, KEYS[2]=pub/sub 채널, ARGV[1]=해제 메시지, ARGV[2]=TTL(ms), ARGV[3]=소유자
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
return nil; -- 내 락 아님
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); -- 재진입 카운트 -1
if (counter > 0) then
redis.call('pexpire', KEYS[1], ARGV[2]); -- 아직 중첩 보유 → TTL만 갱신
return 0;
else
redis.call('del', KEYS[1]); -- 완전 해제
redis.call('publish', KEYS[2], ARGV[1]); -- 대기 스레드 깨우기
return 1;
end;
IllegalMonitorStateExceptionleaseTime을 지정하지 않았을 때만 동작lockWatchdogTimeout = 30초, internalLockLeaseTime / 3 = 10초마다 TTL을 다시 30초로 연장-- 갱신 스크립트: 소유자면 TTL을 다시 밀어줌
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
end;
return 0;
| API | 대기 | 보유 시간 | watchdog |
|---|---|---|---|
lock() |
무한 | 살아있는 한 무한 | ON |
lock(lease, unit) |
무한 | lease 후 강제 해제 |
OFF |
tryLock(wait, unit) |
wait까지 |
살아있는 한 무한 | ON |
tryLock(wait, lease, unit) |
wait까지 |
lease 후 강제 해제 |
OFF |
leaseTime을 명시하면 watchdog이 꺼진다 → 작업이 leaseTime보다 길어지면 작업 중인데도 락이 만료돼 다른 스레드가 진입 → 상호배제가 깨짐leaseTime 없이 쓰면 watchdog이 갱신해 안전하지만, 크래시 후 최대 30초간 락이 잡혀 있음leaseTime을 넘기면 상호배제가 풀려 다른 스레드가 동시 진입 가능 (위 표의 함정)RedissonRedLock(여러 마스터에 Redlock)