Redisson簡介
Redisson在基于NIO的Netty框架上,充分的利用了Redis鍵值數據庫提供的一系列優勢,在Java實用工具包中常用接口的基礎上,為使用者提供了一系列具有分布式特性的常用工具類。使得原本作為協調單機多線程并發程序的工具包獲得了協調分布式多機多線程并發系統的能力,大大降低了設計和研發大規模分布式系統的難度。同時結合各富特色的分布式服務,更進一步簡化了分布式環境中程序相互之間的協作。
https://redisson.org/
https://redisson.org/Redisson.pdf
https://github.com/redisson/redisson/
Redisson實現的分布式鎖
Redisson獲取鎖的方式
RedissonLock
加鎖
- 使用redisson加鎖的代碼示例
public void getlock() {
Config config = new Config();
config.useSingleServer()
.setTimeout(1000000)
.setAddress("redis://127.0.0.1:6379");
RedissonClient redissonClient = Redisson.create(config);
RLock testLock = redissonClient.getLock("test_lock");
// 加鎖
testLock.lock();
try {
Thread.sleep(100000L);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放鎖
testLock.unlock();
}
}
- org.redisson.Redisson#getLock
@Override
public RLock getLock(String name) {
return new RedissonLock(connectionManager.getCommandExecutor(), name);
}
- RedissonLock獲取鎖的源碼部分
org.redisson.RedissonLock#lock(long, java.util.concurrent.TimeUnit, boolean)
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
// 獲取當前線程Id
long threadId = Thread.currentThread().getId();
// 嘗試獲取鎖
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
// 獲取鎖失敗
return;
}
RFuture<RedissonLockEntry> future = subscribe(threadId);
commandExecutor.syncSubscription(future);
try {
while (true) {
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
break;
}
// waiting for message
if (ttl >= 0) {
try {
getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (interruptibly) {
throw e;
}
getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else {
if (interruptibly) {
getEntry(threadId).getLatch().acquire();
} else {
getEntry(threadId).getLatch().acquireUninterruptibly();
}
}
}
} finally {
unsubscribe(future, threadId);
}
}
org.redisson.RedissonLock#tryAcquireAsync
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1) {
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining == null) {
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
org.redisson.RedissonLock#tryLockInnerAsync
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
- 加鎖過程使用的lua腳本(其中KEYS[1]為鎖在redis中的key值,ARGV[1]為鎖的租期,對應redis中hash字段的value值,ARGV[2]為鎖在redis中的hash對象的字段名稱)
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]);
-
redis中鎖的結構
鎖在redis中的結構
釋放鎖
org.redisson.RedissonLock#unlock
@Override
public void unlock() {
try {
get(unlockAsync(Thread.currentThread().getId()));
} catch (RedisException e) {
if (e.getCause() instanceof IllegalMonitorStateException) {
throw (IllegalMonitorStateException) e.getCause();
} else {
throw e;
}
}
}
org.redisson.RedissonLock#unlockAsync(long)
@Override
public RFuture<Void> unlockAsync(long threadId) {
RPromise<Void> result = new RedissonPromise<Void>();
RFuture<Boolean> future = unlockInnerAsync(threadId);
future.onComplete((opStatus, e) -> {
if (e != null) {
// 取消分布式的watchDog續期
cancelExpirationRenewal(threadId);
result.tryFailure(e);
return;
}
if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + threadId);
result.tryFailure(cause);
return;
}
cancelExpirationRenewal(threadId);
result.trySuccess(null);
});
return result;
}
org.redisson.RedissonLock#unlockInnerAsync
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"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;",
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}
- 釋放鎖執行的lua腳本( KEYS[1]:redis中hash對象的key值;KEYS[2]:釋放鎖的channelName;ARGV[1]:LockPubSub.UNLOCK_MESSAGE;ARGV[2]:鎖的過期時間;ARGV[3]:redis中hash對象的Field內容 )
先查詢redis中是否存在這個鎖
如果這個鎖不存在,則直接return;
如果鎖存在,則將鎖的占用-1
如果鎖的引用計數為0,則證明沒有線程/進程占用鎖,刪除鎖,并通知客戶端
如果鎖的引用計數大于0,則證明還有線程/進程在占用鎖,則重新設置鎖的過期時間
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;
鎖續期
如果同時有線程A和線程B在作業。
線程A先獲取到鎖,開始執行業務邏輯,但是線程A的業務邏輯因為種種原因導致在鎖的超時時間內沒有完成。如果沒有鎖續期機制,則會直接丟失鎖
這個時候,線程B獲取到鎖,線程B執行業務的過程中,線程A執行完畢,會嘗試釋放鎖。這個時候可能會將線程B持有的鎖釋放
為了解決這種問題,redisson引入了鎖續期機制
- org.redisson.RedissonLock#renewExpirationAsync
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"end; " +
"return 0;",
Collections.<Object>singletonList(getName()),
internalLockLeaseTime, getLockName(threadId));
}
- 鎖續期執行的lua腳本
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
end;
return 0;
RedLock
上述加鎖、鎖續期、釋放鎖的過程應該可以滿足
redis集群穩定
的大部分場景的需求。
但是還有一種場景:獲取鎖之后,redis集群的master掛了,slaver變為新的master。由于redis的主從同步是異步的,無法保證slave節點的數據跟master的數據是完全一致的。
這個時候,如果某個線程加鎖成功,鎖的信息保存在master上,還未來得及同步到slave上。這個時候master掛了,slave變為新的master。就會出現鎖丟失的問題。等到老的master的恢復之后,會存在鎖被重復獲取的問題。
- 為了解決上述問題,我們可以選擇
RedLock
的方案.Redisson正好就為我們實現的RedLock的整個過程。
org.redisson.RedissonRedLock