什么是分布式鎖
分布式鎖是控制不同系統(tǒng)訪問共享資源的一種鎖機(jī)制,保證共享資源的可用、準(zhǔn)確移稳。
分布式鎖需要具備什么條件
1.互斥(必須):同一時刻,分布式部署的應(yīng)用中会油,同一個方法/資源只能被一臺機(jī)器上的一個線程占用个粱。
2.鎖失效保護(hù)(必須):出現(xiàn)客戶端斷電等異常情況,鎖仍然能被其他客戶端獲取翻翩,防止死鎖都许。
3.可重入(可選):同一個線程在沒有釋放鎖之前稻薇,如果想再次操作,可以直接獲得鎖胶征。
4.阻塞/非阻塞(可選):若沒有獲取到鎖塞椎,返回獲取失敗
5.高可用、高性能(可選):獲取釋放鎖最好是原子操作睛低,獲取釋放鎖的性能要好
分布式鎖的實現(xiàn)有哪些
1.基于數(shù)據(jù)庫實現(xiàn)
2.基于緩存(redis案狠,memcached)實現(xiàn)
3.基于zookeeper實現(xiàn)
redis實現(xiàn)方案
本篇文章我們先來講講redis的實現(xiàn)方案。
version1
lock:SETNX key value
unlock:DEL key [key ...]
指令含義參考:http://doc.redisfans.com/string/setnx.html
這是第一版最簡單的方案钱雷,保證在沒有出現(xiàn)任何異常的時候多個客戶端可以使用分布式鎖骂铁。
但是問題來了,如下圖中所示罩抗,client2在獲取鎖之后突然掛了拉庵,這時候鎖k將無法釋放,其他client就永遠(yuǎn)拿不到這把鎖了套蒂。這就是需要解決的鎖失效保護(hù)問題钞支。
version2
我們可以給鎖引入一個過期時間,這樣即使client2掛了操刀,鎖過期之后其他client仍然能用烁挟。
EXPIRE key seconds
但此時同樣會存在一些問題:
1)誤刪
解決方法是每個client塞給鎖的value設(shè)定為唯一的隨機(jī)字符串,在刪除的時候先get一把骨坑,如果還是這個字符串的話才去刪信夫。
2)過期時間需大于業(yè)務(wù)執(zhí)行時間,不然任務(wù)還沒搞完就被別人搶了
這個時候需要開啟另外一個線程專門去刷新鎖的過期時間卡啰。
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腳本進(jìn)行原子刪除操作
? ? 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
對于阻塞/非阻塞的要求,我們可以根據(jù)自己的業(yè)務(wù)特性杀迹,如果要阻塞亡脸,使用while循環(huán)調(diào)用;如果要非阻塞树酪,這次調(diào)用失敗浅碾,就需要增加事后的補(bǔ)償機(jī)制。
對于可重入的特性续语,在一個線程獲取到鎖之后垂谢,可以把當(dāng)前主機(jī)信息和線程信息保存起來,下次再獲取之前先檢查自己是不是當(dāng)前鎖的擁有者疮茄。
總結(jié)
通過redis實現(xiàn)分布式鎖的必要可選條件之后滥朱,方案基本成型了根暑,這個方案可以提供很好的性能。但是對于超時時間的設(shè)置徙邻,以及集群部署redis避免單點(diǎn)問題等還需要進(jìn)一步優(yōu)化排嫌。