redis--分布式鎖

[TOC]

1. 分布式鎖背景

在單體機器的jvm中,多個線程想要訪問共享資源兴想,那么,需要在jvm中創(chuàng)建一個獨占鎖赡勘,哪個線程獲取到了鎖嫂便,那么這個線程可以訪問資源。其他線程只能等待獲取到鎖的線程釋放鎖闸与。

在多體機器的集群環(huán)境中毙替,仍然是多個線程想要訪問共享資源。但是因為這些線程并不在一個jvm中践樱,所以創(chuàng)建獨占鎖就不能實現(xiàn)不同機器的jvm內(nèi)的線程等待厂画。所以就需要引入第三方鎖,集群內(nèi)的所有jvm都可以訪問第三方鎖拷邢,哪一個機器的得到了鎖袱院,那么這個機器的線程就可以訪問資源,其他機器的線程只能等待釋放鎖瞭稼。

image.png

2. 鎖的基本特性

  • 安全:獨占鎖忽洛。在任意一個時刻,只有一個客戶端持有鎖环肘。
  • 健壯:無死鎖欲虚。即使持有鎖的機器掛了,或者網(wǎng)絡(luò)不可達(dá)悔雹,也不能造成死鎖复哆。
  • 容錯:只要存在一個可用的鎖平臺,那么就能獲取與釋放鎖腌零。

3. 基于Redis實現(xiàn)鎖的基本原理

實現(xiàn)redis分鎖的最簡單的方法就是在redis中創(chuàng)建一個key梯找,這個key有實效時間,保證分布式鎖的健壯性莱没,保證鎖最終會自動釋放初肉,不會出現(xiàn)死鎖。釋放鎖饰躲,就是刪除這個key牙咏。

上述實現(xiàn)看起來還不錯,但是依然存在問題:

假如獲得鎖的線程在超時時間內(nèi)還未處理完成怎么辦嘹裂?

假如redis集群主從復(fù)制失敗了怎么辦妄壶?

這兩個問題都會導(dǎo)致多個線程獲得了鎖,破壞了分布式鎖的安全性寄狼。

4. redis實現(xiàn)鎖

為了解決3中的兩個問題丁寄,可以隨機生成value.

也就是說氨淌,在獲取鎖的時候,使用set key value nx px time來保證只有key不存在時伊磺,才會創(chuàng)建盛正,超時時間是time.超時時間就是線程持有鎖的最大時間。

釋放鎖的時候屑埋,需要驗證當(dāng)前線程釋放的線程是不是自己持有的鎖豪筝。

但是超時時間問題還是沒有解決。

使用set可以存儲字符串摘能,線程在獲取到鎖后续崖,將獲取鎖的時間做為值放入,同時還要加上線程自己的隨機數(shù)团搞,將字符串打造成多個屬性的對象的json串严望。

其他線程在獲取鎖的時候,根據(jù)json串逻恐,判斷像吻,持有鎖的線程是不是死掉了。

舉個例子:約定持有鎖的線程复隆,每隔1分鐘將json里面的值++萧豆。其他線程嘗試獲取鎖的時候,發(fā)現(xiàn)當(dāng)前時間已經(jīng)有多個時間間隔的值沒有更新了昏名,那么就可以認(rèn)為持有鎖的線程掛了。

5. redis分布式鎖

前面我們考慮的都是單體的redis如何實現(xiàn)分布式鎖阵面。

那么如果redis也是多個實例的轻局,這些實例之間完全獨立,沒有主從賦值或者其他集群協(xié)調(diào)样刷。那么前面我們討論的解決方案就不能保證安全了仑扑。

為了實現(xiàn)分布式鎖,我們可以約定==客戶端嘗試向所有的redis實例獲取鎖置鼻,如果至少有2/3的redis獲取鎖成功镇饮,那么就表示這個客戶端獲取分布式鎖成功。鎖需要時間戳和隨機值保證唯一性箕母。==

因為我們的閾值是2/3储藐,不可能同時有多個線程獲取2/3的鎖,而且這些鎖還是同一把鎖嘶是。

為了防止redis實例不可達(dá)钙勃,我們不僅僅需要2/3成功,還需要在獲取鎖的時候聂喇,設(shè)置小于2個數(shù)量級的超時時間辖源。

舉個例子:

我們redis實例有5個,這些redis實例之間沒沒有任何關(guān)系。

接著客戶端得到鎖的key==(所有的鎖的key相同克饶,value不同)==酝蜒。

然后客戶端嘗試向所有的redis實例注冊鎖。

假設(shè)有3個redis實例注冊成功矾湃,此時客戶端持有鎖亡脑。

另一個客戶端在第一個客戶端持有鎖的狀態(tài)下,嘗試獲取鎖洲尊,那么远豺,此時至少有2/3的redis實例的鎖是占有的,那么嘗試獲取鎖的線程就無法滿足2/3的這個閾值了坞嘀,就無法持有鎖了躯护。

嘗試獲取鎖失敗,需要盡快釋放已經(jīng)獲取持有鎖的redis實例丽涩,避免影響下一次獲取鎖棺滞。

假設(shè)鎖的有效時間是10s,那么客戶端和redis的連接超時時間應(yīng)該設(shè)置為100ms <= 在兩個數(shù)量級以上矢渊,否則線程花費80%以上的時間獲取了鎖继准,然后還沒開始使用呢湃密,就超時了薛匪。

==超時續(xù)期==可以使用==EXPIRE==進行續(xù)期。

這個方法能滿足需要遭殉,但是依然不太好毡鉴,因為嘗試獲取鎖的時候崔泵,不是同步的,也就是說猪瞬,無法在同一時間獲取到全部2/3的鎖憎瘸。獲取鎖的過程中,也需要花費一定的時間陈瘦。

所以幌甘,鎖的實際使用時間是不確定的,即使有超時時間痊项,實際可使用的時間也是小于超時時間的锅风。

而且,還存在一個比較致命的問題鞍泉,這些redis實例之間存在==時鐘漂移==遏弱。當(dāng)redis實例之間沒有做時鐘同步,那么因為時鐘漂移問題塞弊,會造成鎖的實際使用時間很可能是不確定的漱逸,往往小于預(yù)期時間泪姨。

6. 獲取鎖失敗

當(dāng)客戶端獲取鎖失敗后,不應(yīng)該立即重試饰抒,一般情況下肮砾,如果因為沖突而無法獲取到鎖,那么失敗后立即重試袋坑,幾乎也是失敗的仗处。因為多個客戶端在同一時間搶奪同一個鎖,會造成==腦裂==枣宫。(為了防止腦裂婆誓,一般解決方式是采用過半策略。得到支持的數(shù)量超過一半才能認(rèn)為是得到整個集群的支持)

所以也颤,客戶端在獲取鎖失敗后洋幻,應(yīng)該等待隨機的時間,然后在嘗試獲取鎖翅娶。

而且還應(yīng)該注意一點文留,當(dāng)獲取失敗后,應(yīng)該盡可能快的釋放已經(jīng)獲取到的鎖竭沫。否則燥翅,在一個超時時間內(nèi),沒有客戶端可以獲取鎖蜕提。

還是延續(xù)前面的例子:我們有5個客戶端森书,5個redis實例。

第一次:每個客戶端得到了一個redis鎖谎势,但是沒有客戶端獲取的redis鎖的數(shù)量超過2/3拄氯,所有客戶端獲取鎖失敗。

第二次:因為客戶端等待隨機的時間它浅,有2個客戶端獲取到了鎖,另外有一個客戶端獲取到了鎖镣煮,其他兩個客戶端沒有獲取到鎖姐霍,因為不滿足2/3的策略,獲取失敗典唇。

多次進行后镊折,到達(dá)了超時時間,依然沒有客戶端獲取到鎖介衔,那么恨胚,這個鎖就是低可用性的鎖,特別是隨著客戶端的數(shù)量的增加炎咖,可用性也會下降赃泡。

==失敗懲罰==

某個客戶端嘗試獲取鎖寒波,當(dāng)?shù)玫?/3的鎖后,發(fā)現(xiàn)剩余的鎖都被占用了升熊,此時客戶端無法獲取鎖俄烁,需要釋放,結(jié)果在釋放一半的時候级野,網(wǎng)絡(luò)中斷了页屠,那么這個客戶端持有的鎖在超時時間內(nèi)就無法釋放了。只能等到超時時間到蓖柔,自動釋放辰企。

==此時這些鎖可以認(rèn)為在這段時間內(nèi)被懲罰了。==

7. 最終

這樣就完美了嗎况鸣?

當(dāng)然不是牢贸,我們前面的==過半策略==是==2/3==如果更小點呢?

假設(shè)現(xiàn)在有100個redis實例懒闷,我們的閾值是60%十减。

因為持續(xù)并發(fā),需要增加redis實例愤估,于是又增加了100個redis實例帮辟。

如果在增加的同時,正好有客戶端在獲取鎖玩焰,那么此時由驹,就有可能存在多個客戶端獲取到鎖的問題。

所以昔园,這個過半策略蔓榄,應(yīng)該是能夠動態(tài)計算的。

  • redis實例崩潰造成鎖在一定的時間內(nèi)不可用

即使這樣默刚,在分布式環(huán)境下甥郑,存在著各種各樣的問題,比如redis實例崩潰荤西,導(dǎo)致鎖本來是空閑的澜搅,但是集群內(nèi)的部分redis實例崩潰了,在進行重啟恢復(fù)的時候邪锌,只恢復(fù)到了鎖持有的狀態(tài)勉躺,此時如果崩潰的機器數(shù)量比較大,就會導(dǎo)致在這部分崩潰的機器的鎖自動釋放前觅丰,沒有任何客戶端可以獲取鎖饵溅。

  • 因網(wǎng)絡(luò)隔離,造成鎖不安全

假設(shè)我們有100個redis實例妇萄,客戶端A現(xiàn)在已經(jīng)獲取到了2/3的鎖66個蜕企,此時咬荷,集群的鎖是占用狀態(tài)。

但是因為動態(tài)削減redis實例糖赔,造成B客戶端在嘗試獲取鎖的時候萍丐,獲取了33個鎖,就滿足過半策略了(假設(shè)從100 -> 48),此時33剛好是48的2/3放典,那么就相當(dāng)于兩個客戶端都獲取到了鎖逝变。

8. 使用

在java語言中,使用的最多的redis鎖應(yīng)該就是redisson了奋构。

redisson

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壳影,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子弥臼,更是在濱河造成了極大的恐慌宴咧,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件径缅,死亡現(xiàn)場離奇詭異掺栅,居然都是意外死亡,警方通過查閱死者的電腦和手機纳猪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門氧卧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人氏堤,你說我怎么就攤上這事沙绝。” “怎么了鼠锈?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵闪檬,是天一觀的道長。 經(jīng)常有香客問我购笆,道長粗悯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任同欠,我火速辦了婚禮样傍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘行您。我一直安慰自己,他們只是感情好剪廉,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布娃循。 她就那樣靜靜地躺著,像睡著了一般斗蒋。 火紅的嫁衣襯著肌膚如雪捌斧。 梳的紋絲不亂的頭發(fā)上笛质,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音捞蚂,去河邊找鬼妇押。 笑死,一個胖子當(dāng)著我的面吹牛姓迅,可吹牛的內(nèi)容都是我干的敲霍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼丁存,長吁一口氣:“原來是場噩夢啊……” “哼肩杈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起解寝,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤扩然,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后聋伦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夫偶,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年觉增,在試婚紗的時候發(fā)現(xiàn)自己被綠了兵拢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡抑片,死狀恐怖卵佛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情敞斋,我是刑警寧澤截汪,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站植捎,受9級特大地震影響衙解,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜焰枢,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一蚓峦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧济锄,春花似錦暑椰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至低滩,卻和暖如春召夹,著一層夾襖步出監(jiān)牢的瞬間岩喷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工监憎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纱意,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓鲸阔,卻偏偏與公主長得像偷霉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子隶债,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345