我們首先來看第乙個問題。為了避免出現這種「長生不老」的鎖,我們肯定需要給它設定乙個過期時間的。你可能會想到使用expire
命令對鎖設定過期時長,但是setnx
和expire
的兩次執行需要是原子性的。為什麼需要是原子性的?還是那句話 「往往在生產環境中會出現各種意想不到的問題」 ,萬一剛執行完setnx
命令機器突然宕機了呢!所以,這兩次操作是需要原子性的。幸好,從redis 2.6.12
開始set
命令增加了可選引數,例如:set key-with-expire-and-nx "hello" ex 10086 nx
。這樣我們在獲取鎖的時候,同時設定乙個過期時長就能避免出現「長生不老」的鎖了。獲取鎖核心**如下:
/**
* 預設鎖最大存活時長
*/private final static int lock_max_timeout_sec = 600;
/** * 加鎖/獲取鎖
* @param key 鎖名稱
* @param identity 標識
* @param expiresec 過期時間
* @return boolean 是否加鎖成功
*/public boolean lock(string key, string identity, int expiresec) catch (exception e) ,出現錯誤{}", key, e);
return false;
}return lock;
}public boolean lock(string key, string identity)
lockb
方法的**,可自行修改。其中expiresec
的大小,也可以根據業務需要設定乙個預設的固定值。技術本身是要為業務服務的,所以並不是一成不變的,**實現上還要具體問題具體分析。
如果不使用上面的方法,並且也不用lua
指令碼還有其他辦法嗎?當然有!不過在實現上就會稍微麻煩些了。前面說的這個問題,是由於「長生鎖」的存在導致,後續執行緒獲取鎖失敗。那麼我們可以在獲取鎖失敗後,讓當前執行緒去看看這個鎖超時了沒?如果超時了,那麼就由當前執行緒直接「結束」它,並且再去獲取鎖。如果發現沒有超時,那就仍然返回false
乖乖等著吧。這個方案如何來實現呢?**如下:
/**
* 預設鎖最大存活時長
*/private final static int lock_max_timeout_sec = 600;
/** * 加鎖/獲取鎖,自定義實現[過期時間]
* @param key 鎖名稱
* @param identity 標識
* @param expiresec 過期時間
* @return boolean 是否加鎖成功
*/public boolean lockv2(string key, string identity, int expiresec)
// 未獲取鎖,檢查鎖狀態
object obj = redistemplate.opsforvalue().get(key);
string value = obj != null ? obj.tostring() : null;
if (value != null) ,已過期", key);
// 釋放鎖
unlockv2(key, value.split(separator)[0]);
// 再次嘗試獲取鎖
return lockv2(key, identity, expiresec);}}
return false;
}public boolean lockv2(string key, string identity)
/** * 釋放鎖
* @param key 鎖名稱
* @param identity 標識
*/public void unlockv2(string key, string identity)
}}catch (exception e)
}
雖然有兩種辦法來解決「長生鎖」的問題,但是還會出現一種極端情況。假如原本3秒就能執行完的定時任務,卻用時 1000秒,遠遠大於鎖的過期時間。所以,就可能又會出現某個執行緒正在持有的鎖,被其它執行緒釋放掉,或者由於expire
設定的時間到期失去對鎖的持有。關於這個問題,一是可以通過對專案中使用到鎖的業務場景進行分析評估,來動態調整鎖的有效期,從而避免出現這種現象,或者即使出現故障也對業務不產生影響或者能夠將影響控制到可承受的範圍內;二是可採用守護執行緒,讓獲取鎖的執行緒開啟乙個守護執行緒,來監控當前任務執行的狀態以及是否應該延長鎖的有效期。當鎖即將到期時但任務還未執行完時,守護執行緒可以自動為其延長時間。當持有鎖的執行緒執行完任務後,會顯示的關掉守護執行緒。配合方案一和二,應該可以應對專案中大部分場景了。
解決完第乙個問題,可以來看下第二個問題了!其實第二個問題的本質就是排他鎖,為了實現在某個時長內只允許所有執行緒獲取一次鎖同樣也離不開「時間」這個變數。我們可以利用在 redis 中儲存乙個和每個排他鎖相關的鍵值對來記錄時間,並決定是否能夠獲取鎖。具體實現:
/**
* 擁有排他時長的鎖
* @param key 鎖名稱
* @param identity 標識
* @param expiresec 過期時間
* @param interval 排它時間
* @return 是否獲取鎖
*/public boolean lock(string key, string identity, int expiresec, int interval)
}setstring(key + "t", nowtimestr);
return true;
}return false;
}
至此,我們把發現的問題都解決完了。剩下的事情就是要梳理梳理**,把不必要對外暴露的底層方法的修飾符改為private
,把相關方法再封裝一下讓curd同學使用起來更方便 。
redis 分布式鎖系列文章中的所有**都已上傳至 github 倉庫: springboot-demo
Redis 分布式鎖 (3)完善這把「鎖」
我們首先來看第乙個問題。為了避免出現這種 長生不老 的鎖,我們肯定需要給它設定乙個過期時間的。你可能會想到使用expire命令對鎖設定過期時長,但是setnx和expire的兩次執行需要是原子性的。為什麼需要是原子性的?還是那句話 往往在生產環境中會出現各種意想不到的問題 萬一剛執行完setnx命令...
redis分布式鎖
redis分布式鎖 直接上 我寫了四個redis分布式鎖的方法,大家可以提個意見 第一種方法 redis分布式鎖 param timeout public void lock long timeout thread.sleep 100 catch exception e override publi...
Redis分布式鎖
分布式鎖一般有三種實現方式 1.資料庫樂觀鎖 2.基於redis的分布式鎖 3.基於zookeeper的分布式鎖.首先,為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件 互斥性。在任意時刻,只有乙個客戶端能持有鎖。不會發生死鎖。即使有乙個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保...