FencedLock
redis lock distributed concurrency
FencedLock
Distributed System 에서 Shared Resources 를 보호하기 위해 Distributed Lock Mechanisms 을 사용한다. 일반적으로 Redis 의 lock/tryLock 을 사용하는데, STOP THE WORLD 가 발생하는 경우에는 Lost Update(write-write-conflict) 가 발생할 수 있다.
Specifically, a write–write conflict occurs when "transaction requests to write an entity for which an unclosed transaction has already made a write request.
Stop The World 에 의한 Lost Update 를 막기 위해서 Fencing Token 을 사용한다.
Redisson 의 FencedLock 은 다음과 같다.
RFencedLock lock = redisson.getFencedLock("myLock");
// traditional lock method
Long token = lock.lockAndGetToken();
// or acquire lock and automatically unlock it after 10 seconds
token = lock.lockAndGetToken(10, TimeUnit.SECONDS);
// or wait for lock aquisition up to 100 seconds
// and automatically unlock it after 10 seconds
Long token = lock.tryLockAndGetToken(100, 10, TimeUnit.SECONDS);
if (token != null) {
try {
// check if token >= old token
...
} finally {
lock.unlock();
}
}
RedissonFencedLock 클래스를 가서 코드를 살펴보면 내부적으로 아래 메서드를 사용하고 있음을 알 수 있고, Lua Script 를 확인할 수 있다.
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
return this.commandExecutor.syncedEval(
this.getRawName(), LongCodec.INSTANCE, command,
"if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then redis.call('incr', KEYS[2]);redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);",
Arrays.asList(this.getRawName(), this.tokenName), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)}
);
}
Lua Script:
-- 조건문
if (
(redis.call('exists', KEYS[1]) == 0) or -- 키가 존재하지 않거나
(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; -- nil 반환
end;
-- 조건이 만족하지 않을 경우
return redis.call('pttl', KEYS[1]); -- 키의 남은 만료 시간 반환
필드:
- KEYS[1]: 락의 키 이름
- ARGV[1]: 만료 시간 (밀리초)
- ARGV[2]: 해시 필드 이름 (보통 스레드나 프로세스 식별자)
조건이 참인 경우:
- hincrby: 해시 필드의 값을 1 증가시킴
- pexpire: 키에 대한 만료 시간을 설정 (밀리초 단위)
- return nil: 성공적으로 처리됨을 나타냄
조건이 거짓일 경우:
- pttl: 키의 남은 만료 시간을 밀리초 단위로 반환
Watch Dog Mechanism
tryLockAndGetTokenAsync:
위 코드에서 if (!subscribeFuture.isDone()) { }
조건문을 보면 WatchDog 과 유사한 패턴이 적용되어있다.