什么是分布式鎖

學(xué)習(xí)完整課程請(qǐng)移步 互聯(lián)網(wǎng) Java 全棧工程師

本節(jié)視頻

概述

為了防止分布式系統(tǒng)中的多個(gè)進(jìn)程之間相互干擾堂油,我們需要一種分布式協(xié)調(diào)技術(shù)來對(duì)這些進(jìn)程進(jìn)行調(diào)度。而這個(gè)分布式協(xié)調(diào)技術(shù)的核心就是來實(shí)現(xiàn)這個(gè)分布式鎖

為什么要使用分布式鎖

  • 成員變量 A 存在 JVM1猾警、JVM2拢锹、JVM3 三個(gè) JVM 內(nèi)存中
  • 成員變量 A 同時(shí)都會(huì)在 JVM 分配一塊內(nèi)存赖临,三個(gè)請(qǐng)求發(fā)過來同時(shí)對(duì)這個(gè)變量操作鲤脏,顯然結(jié)果是不對(duì)的
  • 不是同時(shí)發(fā)過來秸歧,三個(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è)問題,這就是分布式鎖要解決的問題

分布式鎖應(yīng)該具備哪些條件

  • 在分布式系統(tǒng)環(huán)境下纱耻,一個(gè)方法在同一時(shí)間只能被一個(gè)機(jī)器的一個(gè)線程執(zhí)行
  • 高可用的獲取鎖與釋放鎖
  • 高性能的獲取鎖與釋放鎖
  • 具備可重入特性(可理解為重新進(jìn)入芭梯,由多于一個(gè)任務(wù)并發(fā)使用,而不必?fù)?dān)心數(shù)據(jù)錯(cuò)誤)
  • 具備鎖失效機(jī)制弄喘,防止死鎖
  • 具備非阻塞鎖特性玖喘,即沒有獲取到鎖將直接返回獲取鎖失敗

分布式鎖的實(shí)現(xiàn)有哪些

  • Memcached:利用 Memcached 的 add 命令。此命令是原子性操作蘑志,只有在 key 不存在的情況下累奈,才能 add 成功贬派,也就意味著線程得到了鎖。
  • Redis:和 Memcached 的方式類似澎媒,利用 Redis 的 setnx 命令搞乏。此命令同樣是原子性操作,只有在 key 不存在的情況下戒努,才能 set 成功请敦。
  • Zookeeper:利用 Zookeeper 的順序臨時(shí)節(jié)點(diǎn),來實(shí)現(xiàn)分布式鎖和等待隊(duì)列储玫。Zookeeper 設(shè)計(jì)的初衷侍筛,就是為了實(shí)現(xiàn)分布式鎖服務(wù)的。
  • Chubby:Google 公司實(shí)現(xiàn)的粗粒度分布式鎖服務(wù)撒穷,底層利用了 Paxos 一致性算法匣椰。

通過 Redis 分布式鎖的實(shí)現(xiàn)理解基本概念

分布式鎖實(shí)現(xiàn)的三個(gè)核心要素:

加鎖

最簡(jiǎn)單的方法是使用 setnx 命令。key 是鎖的唯一標(biāo)識(shí)端礼,按業(yè)務(wù)來決定命名禽笑。比如想要給一種商品的秒殺活動(dòng)加鎖,可以給 key 命名為 “l(fā)ock_sale_商品ID” 齐媒。而 value 設(shè)置成什么呢?我們可以姑且設(shè)置成 1纷跛。加鎖的偽代碼如下:

setnx(lock_sale_商品ID喻括,1)

當(dāng)一個(gè)線程執(zhí)行 setnx 返回 1,說明 key 原本不存在贫奠,該線程成功得到了鎖唬血;當(dāng)一個(gè)線程執(zhí)行 setnx 返回 0,說明 key 已經(jīng)存在唤崭,該線程搶鎖失敗拷恨。

解鎖

有加鎖就得有解鎖。當(dāng)?shù)玫芥i的線程執(zhí)行完任務(wù)谢肾,需要釋放鎖腕侄,以便其他線程可以進(jìn)入。釋放鎖的最簡(jiǎn)單方式是執(zhí)行 del 指令芦疏,偽代碼如下:

del(lock_sale_商品ID)

釋放鎖之后冕杠,其他線程就可以繼續(xù)執(zhí)行 setnx 命令來獲得鎖。

鎖超時(shí)

鎖超時(shí)是什么意思呢酸茴?如果一個(gè)得到鎖的線程在執(zhí)行任務(wù)的過程中掛掉分预,來不及顯式地釋放鎖,這塊資源將會(huì)永遠(yuǎn)被鎖仔胶础(死鎖)笼痹,別的線程再也別想進(jìn)來配喳。所以,setnxkey 必須設(shè)置一個(gè)超時(shí)時(shí)間凳干,以保證即使沒有被顯式釋放晴裹,這把鎖也要在一定時(shí)間后自動(dòng)釋放。setnx 不支持超時(shí)參數(shù)纺座,所以需要額外的指令息拜,偽代碼如下:

expire(lock_sale_商品ID, 30)

綜合偽代碼如下:

if(setnx(lock_sale_商品ID净响,1) == 1){
    expire(lock_sale_商品ID少欺,30)
    try {
        do something ......
    } finally {
        del(lock_sale_商品ID)
    }
}

存在什么問題

以上偽代碼中存在三個(gè)致命問題

setnxexpire 的非原子性

設(shè)想一個(gè)極端場(chǎng)景,當(dāng)某線程執(zhí)行 setnx馋贤,成功得到了鎖:

setnx 剛執(zhí)行成功赞别,還未來得及執(zhí)行 expire 指令,節(jié)點(diǎn) 1 掛掉了配乓。

這樣一來仿滔,這把鎖就沒有設(shè)置過期時(shí)間,變成死鎖犹芹,別的線程再也無法獲得鎖了崎页。

怎么解決呢?setnx 指令本身是不支持傳入超時(shí)時(shí)間的腰埂,set 指令增加了可選參數(shù)飒焦,偽代碼如下:

set(lock_sale_商品ID,1屿笼,30牺荠,NX)

這樣就可以取代 setnx 指令。

del 導(dǎo)致誤刪

又是一個(gè)極端場(chǎng)景驴一,假如某線程成功得到了鎖休雌,并且設(shè)置的超時(shí)時(shí)間是 30 秒。

如果某些原因?qū)е戮€程 A 執(zhí)行的很慢很慢肝断,過了 30 秒都沒執(zhí)行完杈曲,這時(shí)候鎖過期自動(dòng)釋放,線程 B 得到了鎖胸懈。

隨后鱼蝉,線程 A 執(zhí)行完了任務(wù),線程 A 接著執(zhí)行 del 指令來釋放鎖。但這時(shí)候線程 B 還沒執(zhí)行完,線程A實(shí)際上 刪除的是線程 B 加的鎖牛郑。

怎么避免這種情況呢?可以在 del 釋放鎖之前做一個(gè)判斷洁奈,驗(yàn)證當(dāng)前的鎖是不是自己加的鎖间唉。至于具體的實(shí)現(xiàn),可以在加鎖的時(shí)候把當(dāng)前的線程 ID 當(dāng)做 value利术,并在刪除之前驗(yàn)證 key 對(duì)應(yīng)的 value 是不是自己線程的 ID呈野。

加鎖:

String threadId = Thread.currentThread().getId()
set(key,threadId 印叁,30被冒,NX)

解鎖:

if(threadId .equals(redisClient.get(key))){
    del(key)
}

但是,這樣做又隱含了一個(gè)新的問題轮蜕,判斷和釋放鎖是兩個(gè)獨(dú)立操作昨悼,不是原子性。

出現(xiàn)并發(fā)的可能性

還是剛才第二點(diǎn)所描述的場(chǎng)景跃洛,雖然我們避免了線程 A 誤刪掉 key 的情況率触,但是同一時(shí)間有 A,B 兩個(gè)線程在訪問代碼塊汇竭,仍然是不完美的葱蝗。怎么辦呢?我們可以讓獲得鎖的線程開啟一個(gè)守護(hù)線程细燎,用來給快要過期的鎖“續(xù)航”两曼。

當(dāng)過去了 29 秒,線程 A 還沒執(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í)候桃纯,沒人給它續(xù)命酷誓,也就自動(dòng)釋放了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末态坦,一起剝皮案震驚了整個(gè)濱河市盐数,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伞梯,老刑警劉巖玫氢,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帚屉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡漾峡,警方通過查閱死者的電腦和手機(jī)攻旦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來生逸,“玉大人牢屋,你說我怎么就攤上這事〔郯溃” “怎么了烙无?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掰伸。 經(jīng)常有香客問我皱炉,道長(zhǎng),這世上最難降的妖魔是什么狮鸭? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任合搅,我火速辦了婚禮,結(jié)果婚禮上歧蕉,老公的妹妹穿的比我還像新娘灾部。我一直安慰自己,他們只是感情好惯退,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布赌髓。 她就那樣靜靜地躺著,像睡著了一般催跪。 火紅的嫁衣襯著肌膚如雪锁蠕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天懊蒸,我揣著相機(jī)與錄音荣倾,去河邊找鬼。 笑死骑丸,一個(gè)胖子當(dāng)著我的面吹牛舌仍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播通危,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼铸豁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了菊碟?” 一聲冷哼從身側(cè)響起节芥,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逆害,沒想到半個(gè)月后头镊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體增炭,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年拧晕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隙姿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡厂捞,死狀恐怖输玷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情靡馁,我是刑警寧澤欲鹏,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站臭墨,受9級(jí)特大地震影響赔嚎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胧弛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一尤误、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧结缚,春花似錦损晤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至茵宪,卻和暖如春最冰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背稀火。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工暖哨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人憾股。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓鹿蜀,卻偏偏與公主長(zhǎng)得像箕慧,于是被迫代替她去往敵國(guó)和親服球。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354