
什麼是分布式鎖
分布式鎖是控制不同系統通路共享資源的一種鎖機制,保證共享資源的可用、準确。
分布式鎖需要具備什麼條件
1.互斥(必須):同一時刻,分布式部署的應用中,同一個方法/資源隻能被一台機器上的一個線程占用。
2.鎖失效保護(必須):出現用戶端斷電等異常情況,鎖仍然能被其他用戶端擷取,防止死鎖。
3.可重入(可選):同一個線程在沒有釋放鎖之前,如果想再次操作,可以直接獲得鎖。
4.阻塞/非阻塞(可選):若沒有擷取到鎖,傳回擷取失敗
5.高可用、高性能(可選):擷取釋放鎖最好是原子操作,擷取釋放鎖的性能要好
分布式鎖的實作有哪些
1.基于資料庫實作
2.基于緩存(redis,memcached)實作
3.基于zookeeper實作
redis實作方案
本篇文章,我們先來講講基于redis緩存的實作方案
version1lock:
SETNX key valueunlock:
DEL key [key ...]指令含義參考:http://doc.redisfans.com/string/setnx.html
這是第一版最簡單的方案,保證在沒有出現任何異常的時候多個用戶端可以使用分布式鎖。
但是問題來了,如下圖中所示,client2在擷取鎖之後突然挂了,這時候鎖k将無法釋放,其他client就永遠拿不到這把鎖了。這就是需要解決的鎖失效保護問題。
我們可以給鎖引入一個過期時間,這樣即使client2挂了,鎖過期之後其他client仍然能用。
EXPIRE key seconds但此時同樣會存在一些問題:
1)誤删
解決方法是每個client塞給鎖的value設定為唯一的随機字元串,在删除的時候先get一把,如果還是這個字元串的話才去删。
2)過期時間需大于業務執行時間,不然任務還沒搞完就被别人搶了
這個時候需要開啟另外一個線程專門去重新整理鎖的過期時間。
version3我們需要盡量保證擷取、釋放鎖的操作是原子性的,才能避免極端的異常情況。
原子性地加鎖
SET key uniquevalue NX EX 20
原子性地解鎖
我們可以使用原生的lua腳本
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
// java
public void unlock() {
// 使用lua腳本進行原子删除操作
String checkAndDelScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else " +
"return 0 " +
"end";
jedis.eval(checkAndDelScript, 1, lockKey, lockValue);
}
version4 對于阻塞/非阻塞的要求,我們可以根據自己的業務特性,如果要阻塞,使用while循環調用;如果要非阻塞,這次調用失敗,就需要增加事後的補償機制。
對于可重入的特性,在一個線程擷取到鎖之後,可以把目前主機資訊和線程資訊儲存起來,下次再擷取之前先檢查自己是不是目前鎖的擁有者。
總結
通過redis實作分布式鎖的必要可選條件之後,方案基本成型了,這個方案可以提供很好的性能。但是對于逾時時間的設定,以及叢集部署redis避免單點問題等還需要進一步優化。