Redis分布式鎖怎么玩?
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
? 作者?|??書夢一生
來源 |? urlify.cn/bmIzma
66套java從入門到精通實(shí)戰(zhàn)課程分享
概述
為了防止分布式系統(tǒng)中的多個進(jìn)程之間相互干擾,我們需要一種分布式協(xié)調(diào)技術(shù)來對這些進(jìn)程進(jìn)行調(diào)度。而這個分布式協(xié)調(diào)技術(shù)的核心就是來實(shí)現(xiàn)這個分布式鎖。
為什么要使用分布式鎖

成員變量 A 存在 JVM1、JVM2、JVM3 三個 JVM 內(nèi)存中
成員變量 A 同時都會在 JVM 分配一塊內(nèi)存,三個請求發(fā)過來同時對這個變量操作,顯然結(jié)果是不對的
不是同時發(fā)過來,三個請求分別操作三個不同 JVM 內(nèi)存區(qū)域的數(shù)據(jù),變量 A 之間不存在共享,也不具有可見性,處理的結(jié)果也是不對的
注:該成員變量 A 是一個有狀態(tài)的對象
如果我們業(yè)務(wù)中確實(shí)存在這個場景的話,我們就需要一種方法解決這個問題,這就是分布式鎖要解決的問題
分布式鎖應(yīng)該具備哪些條件
在分布式系統(tǒng)環(huán)境下,一個方法在同一時間只能被一個機(jī)器的一個線程執(zhí)行
高可用的獲取鎖與釋放鎖
高性能的獲取鎖與釋放鎖
具備可重入特性(可理解為重新進(jìn)入,由多于一個任務(wù)并發(fā)使用,而不必?fù)?dān)心數(shù)據(jù)錯誤)
具備鎖失效機(jī)制,防止死鎖
具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗
分布式鎖的實(shí)現(xiàn)有哪些
Memcached:利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情況下,才能 add 成功,也就意味著線程得到了鎖。
Redis:和 Memcached 的方式類似,利用 Redis 的 setnx 命令。此命令同樣是原子性操作,只有在 key 不存在的情況下,才能 set成功。
Zookeeper:利用 Zookeeper 的順序臨時節(jié)點(diǎn),來實(shí)現(xiàn)分布式鎖和等待隊列。Zookeeper 設(shè)計的初衷,就是為了實(shí)現(xiàn)分布式鎖服務(wù)的。
Chubby:Google 公司實(shí)現(xiàn)的粗粒度分布式鎖服務(wù),底層利用了 Paxos 一致性算法。
分布式鎖的Redis實(shí)現(xiàn)
加鎖:
?
String?threadId?=?Thread.currentThread().getId()
set(key,threadId?,30,NX)、
解鎖:
?
if(threadId?.equals(redisClient.get(key))){
????del(key)
}
但是,這樣做又隱含了一個新的問題,判斷和釋放鎖是兩個獨(dú)立操作,不是原子性。
出現(xiàn)并發(fā)的可能性
還是剛才第二點(diǎn)所描述的場景,雖然我們避免了線程 A 誤刪掉 key 的情況,但是同一時間有 A,B 兩個線程在訪問代碼塊,仍然是不完美的。怎么辦呢?我們可以讓獲得鎖的線程開啟一個守護(hù)線程,用來給快要過期的鎖“續(xù)航”。
?
?
當(dāng)過去了 29 秒,線程 A 還沒執(zhí)行完,這時候守護(hù)線程會執(zhí)行 expire 指令,為這把鎖“續(xù)命”20 秒。守護(hù)線程從第 29 秒開始執(zhí)行,每 20 秒執(zhí)行一次。
?
?
當(dāng)線程 A 執(zhí)行完任務(wù),會顯式關(guān)掉守護(hù)線程。
?
?
另一種情況,如果節(jié)點(diǎn) 1 忽然斷電,由于線程 A 和守護(hù)線程在同一個進(jìn)程,守護(hù)線程也會停下。這把鎖到了超時的時候,沒人給它續(xù)命,也就自動釋放了。
?
?
正確實(shí)現(xiàn)寫法如下:
/**
????*?嘗試獲取分布式鎖
????*?@param?jedis?Redis客戶端
????*?@param?lockKey?鎖
????*?@param?requestId?請求標(biāo)識
????*?@param?expireTime?超期時間
????*?@return?是否獲取成功
????*/
???public?static?boolean?tryGetDistributedLock(Jedis?jedis,?String?lockKey,?String?requestId,?int?expireTime)?{
???????String?result?=?jedis.set(lockKey,?requestId,?SET_IF_NOT_EXIST,?SET_WITH_EXPIRE_TIME,?expireTime);
???????if?(LOCK_SUCCESS.equals(result))?{
???????????return?true;
???????}
???????return?false;
???}
?
???/**
????*?釋放分布式鎖
????*?@param?jedis?Redis客戶端
????*?@param?lockKey?鎖
????*?@param?requestId?請求標(biāo)識
????*?@return?是否釋放成功
????*/
???public?static?boolean?releaseDistributedLock(Jedis?jedis,?String?lockKey,?String?requestId)?{
???????String?script?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('del',?KEYS[1])?else?return?0?end";
???????Object?result?=?jedis.eval(script,?Collections.singletonList(lockKey),?Collections.singletonList(requestId));
???????if?(RELEASE_SUCCESS.equals(result))?{
???????????return?true;
???????}
???????return?false;
???}
對比
數(shù)據(jù)庫分布式鎖實(shí)現(xiàn)
缺點(diǎn):1.db操作性能較差,并且有鎖表的風(fēng)險
2.非阻塞操作失敗后,需要輪詢,占用cpu資源;
3.長時間不commit或者長時間輪詢,可能會占用較多連接資源
Redis(緩存)分布式鎖實(shí)現(xiàn)
缺點(diǎn):1.鎖刪除失敗 過期時間不好控制
2.非阻塞,操作失敗后,需要輪詢,占用cpu資源;
ZK分布式鎖實(shí)現(xiàn)
缺點(diǎn):性能不如redis實(shí)現(xiàn),主要原因是寫操作(獲取鎖釋放鎖)都需要在Leader上執(zhí)行,然后同步到follower。
總之:ZooKeeper有較好的性能和可靠性。
從理解的難易程度角度(從低到高)數(shù)據(jù)庫 > 緩存 > Zookeeper
從實(shí)現(xiàn)的復(fù)雜性角度(從低到高)Zookeeper >= 緩存 > 數(shù)據(jù)庫
從性能角度(從高到低)緩存 > Zookeeper >= 數(shù)據(jù)庫
從可靠性角度(從高到低)Zookeeper > 緩存 > 數(shù)據(jù)庫
粉絲福利:實(shí)戰(zhàn)springboot+CAS單點(diǎn)登錄系統(tǒng)視頻教程免費(fèi)領(lǐng)取
???
?長按上方微信二維碼?2 秒 即可獲取資料
感謝點(diǎn)贊支持下哈?
