TIL

09. Redisson 분산락 내부

락 획득 — Hash + Lua

-- 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 반환

대기 — pub/sub + Semaphore (스핀하지 않음)

락 해제 — Lua

-- 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;

watchdog — 자동 갱신

-- 갱신 스크립트: 소유자면 TTL을 다시 밀어줌
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return 1;
end;
return 0;

leaseTime 지정 vs watchdog (가장 중요)

API 대기 보유 시간 watchdog
lock() 무한 살아있는 한 무한 ON
lock(lease, unit) 무한 lease 후 강제 해제 OFF
tryLock(wait, unit) wait까지 살아있는 한 무한 ON
tryLock(wait, lease, unit) wait까지 lease 후 강제 해제 OFF

한계와 주의점

참고