redisson 분포식 자물쇠 원리 실현
10442 단어 분포식 자물쇠
이전의 주석 기반 자물쇠는 기본적인 Redis의 분포식 자물쇠였다. 자물쇠의 실현은 Redisson 구성 요소가 제공하는 RLock을 바탕으로 했다. 이 부분에서 Redisson이 자물쇠를 어떻게 실현하는지 살펴보자.
서로 다른 버전이 자물쇠를 실현하는 메커니즘은 결코 같지 않다
인용된 Redisson이 최근에 발표한 버전 3.2.3은 서로 다른 버전이 자물쇠를 실현할 수 있는 메커니즘이 같지 않다. 초기 버전은 간단한 setnx, getset 등 일반적인 명령으로 설정된 것 같았고 후기에 Redis가 스크립트 Lua를 지원하여 실현 원리를 변경했다.
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.2.3</version>
</dependency>
setnx는 getset과 업무를 협조하여 완성해야 자물쇠 문제를 비교적 잘 피할 수 있습니다. 새로운 버전은 루아 스크립트를 지원하기 때문에 업무를 피하고 여러 개의 redis 명령을 조작할 수 있으며 의미 표현이 더욱 명확합니다.RLOCK 인터페이스의 특징
상속 표준 인터페이스 Lock
표준 자물쇠 인터페이스의 모든 기능을 가지고 있습니다. 예를 들어lock,unlock,trylock 등입니다.
확장 표준 인터페이스 Lock
많은 방법을 확장했는데 주로 강제 자물쇠 방출, 유효기간이 있는 자물쇠, 그리고 비동기적인 방법이 있다.그 중에서 앞의 두 가지 방법은 주로 표준lock이 초래할 수 있는 자물쇠 문제를 해결하는 것이다.예를 들어 어떤 라인이 자물쇠를 얻은 후에 라인이 있는 기계가 다운되었다. 이때 자물쇠를 얻은 라인이 자물쇠를 정상적으로 방출하지 못해 나머지 자물쇠를 기다리는 라인이 계속 기다린다.
재입력 가능 메커니즘
각 버전의 구현은 차이가 있다. 재입력은 주로 성능을 고려한다. 같은 라인이 자물쇠를 풀지 않았을 때 다시 자물쇠 자원을 신청하면 신청 절차를 밟지 않고 이미 가져온 자물쇠를 계속 되돌려주고 재입력한 횟수를 기록하면 된다. jdk의 ReentrantLock 기능과 유사하다.리셋 횟수는 hincrby 명령에 따라 사용됩니다. 상세한 매개 변수 아래의 코드입니다.
어떻게 같은 노선이라고 판단합니까?
redisson의 방안은 RedissonLock의 실례적인 guid에 현재 라인의 id를 추가하여 getLockName을 통해 되돌아오는 것입니다.
public class RedissonLock extends RedissonExpirable implements RLock {
final UUID id;
protected RedissonLock(CommandExecutor commandExecutor, String name, UUID id) {
super(commandExecutor, name);
this.internalLockLeaseTime = TimeUnit.SECONDS.toMillis(30L);
this.commandExecutor = commandExecutor;
this.id = id;
}
String getLockName(long threadId) {
return this.id + ":" + threadId;
}
RLOCK 자물쇠를 가져오는 두 장면여기서tryLock의 원본 코드를 보면tryAcquire 방법은 자물쇠를 신청하고 자물쇠의 유효기간이 남은 시간을 되돌려주는 것입니다. 자물쇠가 다른 라인에 신청되지 않은 것을 공백으로 설명하기 위해 직접 가져오고 되돌려줍니다. 시간을 얻으면 경쟁 논리에 들어갑니다.
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
final long threadId = Thread.currentThread().getId();
Long ttl = this.tryAcquire(leaseTime, unit);
if(ttl == null) {
//
return true;
} else {
//
}
}
경쟁 없음, 직접 잠금 해제먼저 자물쇠를 확보하고 자물쇠 뒤에 있는 레디스가 무엇을 하고 있는지 확인하고, 레디스의 모니터를 이용하여 백그라운드에서 레디스의 실행 상황을 감시할 수 있다.@RequestLockable을 추가하는 방법을 사용한 후에 사실은 lock과 unlock을 호출하는 것입니다. 다음은 redis 명령입니다.
자물쇠를 채우다
높은 버전의redis는 루아 스크립트를 지원하기 때문에redisson도 이를 지원했고 스크립트 모드를 사용했습니다. 루아 스크립트에 익숙하지 않은 사람은 찾을 수 있습니다.lua 명령을 실행하는 논리는 다음과 같습니다.
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "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; 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(this.getName()), new Object[]{Long.valueOf(this.internalLockLeaseTime), this.getLockName(threadId)});
}
잠금 프로세스:
"EVAL"
"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;
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]);"
"1" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"
"1000" "346e1eb8-5bfd-4d49-9870-042df402f248:21"
위의 루아 스크립트는 진정한 레디스 명령으로 변환되며, 아래는 루아 스크립트 연산을 거친 후에 실제 실행되는 레디스 명령입니다.
1486642677.053488 [0 lua] "exists" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"
1486642677.053515 [0 lua] "hset" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"
"346e1eb8-5bfd-4d49-9870-042df402f248:21" "1"
1486642677.053540 [0 lua] "pexpire" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0" "1000"
잠금 해제잠금 해제 프로세스가 복잡해 보입니다.
"EVAL"
"if (redis.call('exists', KEYS[1]) == 0) then
redis.call('publish', KEYS[2], ARGV[1]);
return 1; end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
return nil;end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0;
else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1; end;
return nil;"
"2" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"
"redisson_lock__channel:{lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0}"
"0" "1000"
"346e1eb8-5bfd-4d49-9870-042df402f248:21"
경쟁 조건 없이 redis 잠금 해제 명령:대기 대기열의 라인을 깨우쳐 잠금 해제 메시지를 보냅니다.
1486642678.493691 [0 lua] "exists" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"
1486642678.493712 [0 lua] "publish" "redisson_lock__channel:{lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0}" "0"
경쟁경쟁이 있는 경우 레디스 측의 루아 스크립트는 같지만 서로 다른 조건이 서로 다른 레디스 명령을 실행하고 복잡한 레디스슨의 원본 코드에 있다.tryAcquire를 통해 자물쇠가 다른 라인에 요청된 것을 발견하면 경쟁 논리에 들어가야 합니다.
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
final long threadId = Thread.currentThread().getId();
Long ttl = this.tryAcquire(leaseTime, unit);
if(ttl == null) {
return true;
} else {
//
time -= System.currentTimeMillis() - current;
if(time <= 0L) {
return false;
} else {
current = System.currentTimeMillis();
final RFuture subscribeFuture = this.subscribe(threadId);
if(!this.await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
if(!subscribeFuture.cancel(false)) {
subscribeFuture.addListener(new FutureListener() {
public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
if(subscribeFuture.isSuccess()) {
RedissonLock.this.unsubscribe(subscribeFuture, threadId);
}
}
});
}
return false;
} else {
boolean var16;
try {
time -= System.currentTimeMillis() - current;
if(time <= 0L) {
boolean currentTime1 = false;
return currentTime1;
}
do {
long currentTime = System.currentTimeMillis();
ttl = this.tryAcquire(leaseTime, unit);
if(ttl == null) {
var16 = true;
return var16;
}
time -= System.currentTimeMillis() - currentTime;
if(time <= 0L) {
var16 = false;
return var16;
}
currentTime = System.currentTimeMillis();
if(ttl.longValue() >= 0L && ttl.longValue() < time) {
this.getEntry(threadId).getLatch().tryAcquire(ttl.longValue(), TimeUnit.MILLISECONDS);
} else {
this.getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
} while(time > 0L);
var16 = false;
} finally {
this.unsubscribe(subscribeFuture, threadId);
}
return var16;
}
}
}
}
순환 시도에는 일반적으로 다음과 같은 몇 가지 방법이 있습니다.redisson은 자물쇠뿐만 아니라 많은 클라이언트가 redis를 조작하는 방법을 제공하기 때문에 다른 프레임워크, 예를 들어 넷티에 의존한다. 만약에 간단하게 자물쇠를 사용해도 스스로 실현할 수 있다.
이상은 본문의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 일정한 도움을 줄 수 있는 동시에 저희를 많이 지지해 주시기 바랍니다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Zookeeper 분산 잠금 구현무엇이 분포식 자물쇠입니까?분포식 자물쇠는 분포식 시스템이나 서로 다른 시스템 간에 공유 자원에 공동으로 접근하는 것을 제어하는 자물쇠의 일종이다. 만약에 서로 다른 시스템이나 같은 시스템의 서로 다른 호스트 간에 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.