redis分散式鎖-SETNX實現
Redis有一系列的命令,特點是以NX結尾,NX是Not eXists的縮寫,如SETNX命令就應該理解為:SET if Not eXists。這系列的命令非常有用,這裡講使用SETNX來實現分散式鎖。
用SETNX實現分散式鎖
利用SETNX非常簡單地實現分散式鎖。例如:某客戶端要獲得一個名字foo的鎖,客戶端使用下面的命令進行獲取:
SETNX lock.foo <current Unix time + lock timeout + 1>
如返回1,則該客戶端獲得鎖,把lock.foo的鍵值設定為時間值表示該鍵已被鎖定,該客戶端最後可以通過DEL lock.foo來釋放該鎖。
如返回0,表明該鎖已被其他客戶端取得,這時我們可以先返回或進行重試等對方完成或等待鎖超時。
解決死鎖
上面的鎖定邏輯有一個問題:如果一個持有鎖的客戶端失敗或崩潰了不能釋放鎖,該怎麼解決?我們可以通過鎖的鍵對應的時間戳來判斷這種情況是否發生了,如果當前的時間已經大於lock.foo的值,說明該鎖已失效,可以被重新使用。
發生這種情況時,可不能簡單的通過DEL來刪除鎖,然後再SETNX一次,當多個客戶端檢測到鎖超時後都會嘗試去釋放它,這裡就可能出現一個競態條件,讓我們模擬一下這個場景:
C0操作超時了,但它還持有著鎖,C1和C2讀取lock.foo檢查時間戳,先後發現超時了。
C1 傳送DEL lock.foo
C1 傳送SETNX lock.foo 並且成功了。
C2 傳送DEL lock.foo
C2 傳送SETNX lock.foo 並且成功了。
這樣一來,C1,C2都拿到了鎖!問題大了!
幸好這種問題是可以避免的,讓我們來看看C3這個客戶端是怎樣做的:
C3傳送SETNX lock.foo 想要獲得鎖,由於C0還持有鎖,所以Redis返回給C3一個0
C3傳送GET lock.foo 以檢查鎖是否超時了,如果沒超時,則等待或重試。
反之,如果已超時,C3通過下面的操作來嘗試獲得鎖:
GETSET lock.foo <current Unix time + lock timeout + 1>
通過GETSET,C3拿到的時間戳如果仍然是超時的,那就說明,C3如願以償拿到鎖了。
如果在C3之前,有個叫C4的客戶端比C3快一步執行了上面的操作,那麼C3拿到的時間戳是個未超時的值,這時,C3沒有如期獲得鎖,需要再次等待或重試。留意一下,儘管C3沒拿到鎖,但它改寫了C4設定的鎖的超時值,不過這一點非常微小的誤差帶來的影響可以忽略不計。
注意:為了讓分散式鎖的演算法更穩鍵些,持有鎖的客戶端在解鎖之前應該再檢查一次自己的鎖是否已經超時,再去做DEL操作,因為可能客戶端因為某個耗時的操作而掛起,操作完的時候鎖因為超時已經被別人獲得,這時就不必解鎖了。
示例虛擬碼
根據上面的程式碼,我寫了一小段Fake程式碼來描述使用分散式鎖的全過程:
# get lock
lock = 0
while lock != 1:
timestamp = current Unix time + lock timeout + 1
lock = SETNX lock.foo timestamp
if lock == 1 or (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)):
break;
else:
sleep(10ms)
# do your job
do_job()
# release
if now() < GET lock.foo:
DEL lock.foo
是的,要想這段邏輯可以重用,使用python的你馬上就想到了Decorator,而用Java的你是不是也想到了那誰?AOP + annotation?行,怎樣舒服怎樣用吧,別重複程式碼就行。
java之jedis實現
expireMsecs 鎖持有超時,防止執行緒在入鎖以後,無限的執行下去,讓鎖無法釋放
timeoutMsecs 鎖等待超時,防止執行緒飢餓,永遠沒有入鎖執行程式碼的機會
/**
* Acquire lock.
*
* @param jedis
* @return true if lock is acquired, false acquire timeouted
* @throws InterruptedException
* in case of thread interruption
*/
public synchronized boolean acquire(Jedis jedis) throws InterruptedException {
int timeout = timeoutMsecs;
while (timeout >= 0) {
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires); //鎖到期時間
if (jedis.setnx(lockKey, expiresStr) == 1) {
// lock acquired
locked = true;
return true;
}
String currentValueStr = jedis.get(lockKey); //redis裡的時間
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
//判斷是否為空,不為空的情況下,如果被其他執行緒設定了值,則第二個條件判斷是過不去的
// lock is expired
String oldValueStr = jedis.getSet(lockKey, expiresStr);
//獲取上一個鎖到期時間,並設定現在的鎖到期時間,
//只有一個執行緒才能獲取上一個線上的設定時間,因為jedis.getSet是同步的
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
//如過這個時候,多個執行緒恰好都到了這裡,但是隻有一個執行緒的設定值和當前值相同,他才有權利獲取鎖
// lock acquired
locked = true;
return true;
}
}
timeout -= 100;
Thread.sleep(100);
}
return false;
}
一般用法
其中很多繁瑣的邊緣程式碼
包括:異常處理,釋放資源等等
JedisPool pool;
JedisLock jedisLock = new JedisLock(pool.getResource(), lockKey, timeoutMsecs, expireMsecs);
try {
if (jedisLock.acquire()) { // 啟用鎖
//執行業務邏輯
} else {
logger.info("The time wait for lock more than [{}] ms ", timeoutMsecs);
}
} catch (Throwable t) {
// 分散式鎖異常
logger.warn(t.getMessage(), t);
} finally {
if (jedisLock != null) {
try {
jedisLock.release();// 則解鎖
} catch (Exception e) {
}
}
if (jedis != null) {
try {
pool.returnResource(jedis);// 還到連線池裡
} catch (Exception e) {
}
}
}
犀利用法
用匿名類來實現,程式碼非常簡潔
至於SimpleLock的實現
SimpleLock lock = new SimpleLock(key);
lock.wrap(new Runnable() {
@Override
public void run() {
//此處程式碼是鎖上的
}
});
相關文章
- 分散式鎖----Redis實現分散式Redis
- Redis實現分散式鎖Redis分散式
- Redis實現分散式鎖(setnx、getset、incr)以及如何處理超時情況KJBPRedis分散式
- 【Redis】利用 Redis 實現分散式鎖Redis分散式
- 分散式鎖之Redis實現分散式Redis
- 利用Redis實現分散式鎖Redis分散式
- 分散式鎖實現(一):Redis分散式Redis
- Redis之分散式鎖實現Redis分散式
- Redis如何實現分散式鎖Redis分散式
- redis分散式鎖的實現Redis分散式
- redis分散式鎖-java實現Redis分散式Java
- 基於redis實現分散式鎖Redis分散式
- Redis優雅實現分散式鎖Redis分散式
- 實現一個 Redis 分散式鎖Redis分散式
- 如何使用Redis實現分散式鎖Redis分散式
- 用 Go + Redis 實現分散式鎖GoRedis分散式
- redis分散式鎖實現(golang版)Redis分散式Golang
- 如何用 Redis 實現分散式鎖Redis分散式
- Redis面試系列:Redis實現分散式鎖Redis面試分散式
- 利用Python+Redis實現分散式鎖PythonRedis分散式
- Redis分散式鎖的正解實現方式Redis分散式
- 使用Redis分散式鎖實現主備Redis分散式
- Spring Boot Redis 實現分散式鎖,真香!!Spring BootRedis分散式
- Redis實現可重入的分散式鎖Redis分散式
- 實現一個redis的分散式鎖Redis分散式
- Redis實現分散式鎖那件事Redis分散式
- Redis分散式鎖的原理和實現Redis分散式
- Redis分散式鎖實現Redisson 15問Redis分散式
- redis實現分散式鎖天然的缺陷Redis分散式
- Redis分散式鎖實戰Redis分散式
- 分散式鎖與實現(一)基於Redis實現!分散式Redis
- Redis、Zookeeper實現分散式鎖——原理與實踐Redis分散式
- redis分散式鎖-spring boot aop+自定義註解實現分散式鎖Redis分散式Spring Boot
- Redis分散式鎖的正確實現方式Redis分散式
- 基於redis分散式鎖實現“秒殺”Redis分散式
- 再談分散式鎖之剖析Redis實現分散式Redis
- Redis分散式鎖的使用與實現原理Redis分散式
- 基於Redis實現一個分散式鎖Redis分散式