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

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