redis 분포 식 잠 금 프레임 워 크 redisson 기반(^ ^)
9872 단어 자바 분산 구조
Redisson 은 Redis 를 바탕 으로 실 현 된 자바 메모리 데이터 그리드(In-memory Data Grid)입 니 다.그것 은 일련의 분포 식 자바 상용 대상 을 제공 할 뿐만 아니 라 많은 분포 식 서 비 스 를 제공 했다.그 중에서(BitSet,Set,Multimap,SortedSet,Map,List,Queue,BlockingQueue,Deque,BlockingDeque,Semaphore,Lock,AtomicLong,Countdown Latch,Publish/Subscribe,Bloom filter,Remote service,Spring cache,Executor service,Live Object service,Scheduler service)Redisson 은 Redis 를 사용 하 는 가장 간단 하고 편리 한 방법 을 제공 했다.리 디 슨 의 취 지 는 사용자 들 이 리 디 스에 대한 관심 분 리 를 촉진(Separation of Concern)함으로써 사용자 들 이 업무 논리 처리 에 더욱 집중 할 수 있 도록 하 는 것 이다.
분포 식 자물쇠 에 대한 요구 상호 배척 성:분포 식 자 물 쇠 는 서로 다른 노드 의 서로 다른 라인 에서 상호 배척 을 확보 해 야 한다.이것 이 가장 근본 적 인 것 이다.재 접근 성:같은 노드 의 같은 스 레 드 가 자 물 쇠 를 가 져 오 면 이 자 물 쇠 를 다시 가 져 올 수 있 습 니 다.잠 금 시간 초과:로 컬 잠 금 과 마찬가지 로 잠 금 시간 초 과 를 지원 하여 잠 금 을 방지 합 니 다.높 은 사용 가능:잠 금 을 추가 하고 잠 금 을 풀 려 면 효율 적 이 어야 하 며 분포 식 잠 금 의 실 효 를 방지 하고 강등 을 증가 할 수 있 도록 해 야 한다.차단 과 비 차단 지원:ReentrantLock 과 마찬가지 로 lock 과 trylock,try Lock(long time Out)을 지원 합 니 다.공평 한 자물쇠 와 불공평 한 자 물 쇠 를 지원 합 니 다(선택 가능):공평 한 자 물 쇠 는 자 물 쇠 를 요청 하 는 순서에 따라 자 물 쇠 를 얻 는 것 을 의미 합 니 다.불공평 한 자 물 쇠 는 반대로 무질서 합 니 다.이것 은 일반적으로 실현 되 는 것 이 비교적 적다.
tryLock()방법 은 반환 값 이 있 습 니 다.자 물 쇠 를 가 져 오 려 고 시도 하 는 데 사 용 됩 니 다.성공 하면 true 로 돌아 갑 니 다.만약 에 자 물 쇠 를 가 져 오 는 데 실패 하면 false 로 돌아 갑 니 다.이 방법 은 어떻게 든 바로 돌아 갑 니 다.자 물 쇠 를 못 가 져 올 때 계속 기다 리 지 는 않 을 겁 니 다.
Redisson 분포 식 잠 금 실현
자물쇠 의 종류
재 접속 가능 자물쇠
RLock lock = redisson.getLock("anyLock");
//
lock.lock();
...
lock.unlock()
// 10
// unlock
lock.lock(10, TimeUnit.SECONDS);
// , 100 , 10
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
공평자물쇠
RLock fairLock = redisson.getFairLock("anyLock");
//
fairLock.lock();
// 10
// unlock
fairLock.lock(10, TimeUnit.SECONDS);
// , 100 , 10
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();
Redisson 은 분포 식 으로 공평 한 자물쇠 에 다시 들 어 갈 수 있 도록 비동기 실행 에 관 한 방법 도 제공 했다.
RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
연동
Redis 기반 Redisson 분포 식 잠 금 Redisson MultiLock 대상 은 여러 개의 RLock 대상 을 하나의 잠 금 으로 연결 할 수 있 습 니 다.모든 RLock 대상 인 스 턴 스 는 서로 다른 Redisson 인 스 턴 스 에서 나 올 수 있 습 니 다.
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// :lock1 lock2 lock3
// 。
lock.lock();
...
lock.unlock();
붉 은 자물쇠
Redis 의 Redisson 빨 간 자물쇠 Redisson RedLock 대상 을 바탕 으로 Redlock 이 소개 한 잠 금 알고리즘 을 실현 했다.이 대상 은 여러 개의 RLock 대상 을 하나의 빨 간 자물쇠 로 연결 하 는 데 도 사용 할 수 있 습 니 다.모든 RLock 대상 인 스 턴 스 는 서로 다른 Redisson 인 스 턴 스 에서 나 올 수 있 습 니 다.
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// :lock1 lock2 lock3
// 。
lock.lock();
...
lock.unlock();
또한 Redisson 은 잠 금 을 추가 하 는 방법 으로 leaseTime 의 인 자 를 제공 하여 잠 금 을 추가 하 는 시간 을 지정 했다.이 시간 이 지나 자 자물쇠 가 자동 으로 풀 렸 다.
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// lock1,lock2,lock3 , ,10
lock.lock(10, TimeUnit.SECONDS);
// 100 , 10
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
읽 기와 쓰기 자물쇠
RReadWriteLock rwlock = redisson.getLock("anyRWLock");
//
rwlock.readLock().lock();
//
rwlock.writeLock().lock();
신호 량
RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
//
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
//
semaphore.releaseAsync();
만 료 가능 신호 량
Redis 기반 Redisson 만 료 가능 신 호 량(PermitExpirableSemaphore)은 RSemaphore 대상 을 바탕 으로 모든 신호 에 만 료 시간 을 추가 합 니 다.모든 신 호 는 독립 된 ID 를 통 해 식별 할 수 있 으 며,방출 시 이 ID 를 제출 해야만 방출 할 수 있다.
RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();
// , 2 。
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
폐쇄
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();
// JVM
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
자물쇠 의 원리
Redisson 에서 key 를 사용 하여 잠 금 여 부 를 표시 합 니 다.getLock(String key)방법 으로 해당 하 는 자 물 쇠 를 얻 은 후에 이 key 는 하나의 자물쇠 로 Redis 클 러 스 터 에 저 장 됩 니 다.다음 에 다른 스 레 드 가 key 라 는 자 물 쇠 를 얻 으 려 고 시도 할 때 클 러 스 터 에 조회 합 니 다.이 자 물 쇠 를 찾 아 해당 하 는 value 의 값 이 0 이 아 닌 것 을 발견 할 수 있다 면.다른 스 레 드 가 이 자 물 쇠 를 신청 하 는 동시에 풀 리 지 않 으 면 현재 스 레 드 가 차단 에 들 어 갑 니 다.그렇지 않 으 면 현재 스 레 드 에서 이 자 물 쇠 를 가 져 오고 value 값 을 하나 더 추가 합 니 다.잠 금 을 다시 넣 을 수 있다 면 현재 스 레 드 가 자신의 스 레 드 자 물 쇠 를 얻 을 때마다 value 의 값 을 하나 더 추가 하고 자 물 쇠 를 풀 때마다 value 값 을 0 으로 줄 입 니 다.이 자 물 쇠 를 완전히 풀 어 라.밑바닥 은 분포 식 Redis 군집 을 바탕 으로 하기 때문에 Redisson 은 분포 식 잠 금 체 제 를 실현 했다.
자 물 쇠 를 채우다
Redisson 에서 자 물 쇠 를 추가 하려 면 다음 과 같은 세 가지 인자 가 필요 합 니 다.
KEYS[1]:잠 금 이 필요 한 key 입 니 다.문자열 형식 이 필요 합 니 다.
ARGV[1]:잠 금 시간 초과,잠 금 방지
ARGV[2]:자물쇠 의 유일한 표식,id(UUID.randomUUID())+":"+threadId
Future tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
// key , , value=1
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// , key field value
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
//
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
//
"return redis.call('pttl', KEYS[1]);",
Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
자 물 쇠 를 풀다
잠 금 해제 의 논 리 는 상대 적 으로 간단 하 며 구체 적 인 절 차 는 다음 과 같다.
KEYS[1]:잠 금 이 필요 한 key 입 니 다.문자열 형식 이 필요 합 니 다.
KEYS[2]:redis 메시지 의 Channel Name,분포 식 잠 금 에 대응 하 는 유일한 채널 Name:"redissonlock__channel__{” + getName() + “}”
ARGV[1]:reids 메시지 체,여 기 는 하나의 바이트 만 표시 하면 됩 니 다.주로 redis 를 표시 하 는 key 가 잠 금 을 풀 었 고 redis 의 Subscribe 와 결합 하여 잠 금 해제 메 시 지 를 구독 하 는 다른 클 라 이언 트 스 레 드 신청 자 물 쇠 를 깨 울 수 있 습 니 다.
ARGV[2]:잠 금 시간 초과,잠 금 방지
ARGV[3]:자물쇠 의 유일한 표지,즉 방금 소개 한 id(UUID.randomUUID()+":"+threadId
public void unlock() {
Boolean opStatus = commandExecutor.evalWrite(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// key , , (publihs)redis
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
// key field , , 。
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
// counter>0 , key
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
// key publish
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId()));
if (opStatus == null) {
throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + Thread.currentThread().getId());
}
// expire
if (opStatus) {
cancelExpirationRenewal();
}
}
주의 점
Redisson 의 기본 CommandExecutor 구현 은 eval 명령 을 통 해 Lua 스 크 립 트 를 실행 하기 때문에 Redis 버 전 은 2.6 이상 이 어야 합 니 다.그렇지 않 으 면 스스로 이 루어 질 수 있 습 니 다.