跳躍學(xué)習(xí)一下 Redis -- Redis分布式鎖問題專題

30 | 如何使用Redis實(shí)現(xiàn)分布式鎖?

1囚巴、前言

Redis 屬于分布式系統(tǒng)原在,當(dāng)有多個(gè)客戶端需要爭搶鎖時(shí),我們必須要保證彤叉,這把鎖不能是某個(gè)客戶端本地的鎖庶柿。否則的話,其它客戶端是無法訪問這把鎖的姆坚,當(dāng)然也就不能獲取這把鎖了澳泵。

所以,在分布式系統(tǒng)中兼呵,當(dāng)有多個(gè)客戶端需要獲取鎖時(shí)兔辅,我們需要分布式鎖。此時(shí)击喂,鎖是保存在一個(gè)共享存儲(chǔ)系統(tǒng)中的维苔,可以被多個(gè)客戶端共享訪問和獲取

2懂昂、單機(jī)上的鎖和分布式鎖的聯(lián)系與區(qū)別

對(duì)于在單機(jī)上運(yùn)行的多線程程序來說介时,鎖本身可以用一個(gè)變量表示。

變量值為 0 時(shí)凌彬,表示沒有線程獲取鎖沸柔;

變量值為 1 時(shí),表示已經(jīng)有線程獲取到鎖了铲敛。

單機(jī)上加鎖和釋放鎖過程代碼:

acquire_lock(){

????if lock == 0

????????lock = 1

????????return 1

????else

????????return 0

}?

release_lock(){?

????lock = 0?

????return 1

}??

和單機(jī)上的鎖類似褐澎,分布式鎖同樣可以用一個(gè)變量來實(shí)現(xiàn)》ソ客戶端加鎖和釋放鎖的操作邏輯工三,也和單機(jī)上的加鎖和釋放鎖操作邏輯一致:加鎖時(shí)同樣需要判斷鎖變量的值迁酸,根據(jù)鎖變量值來判斷能否加鎖成功;釋放鎖時(shí)需要把鎖變量值設(shè)置為 0俭正,表明客戶端不再持有鎖奸鬓。

和線程在單機(jī)上操作鎖不同的是,在分布式場景下掸读,鎖變量需要由一個(gè)共享存儲(chǔ)系統(tǒng)來維護(hù)串远,只有這樣,多個(gè)客戶端才可以通過訪問共享存儲(chǔ)系統(tǒng)來訪問鎖變量儿惫。相應(yīng)的抑淫,加鎖和釋放鎖的操作就變成了讀取、判斷和設(shè)置共享存儲(chǔ)系統(tǒng)中的鎖變量值姥闪。

so始苇,分布式鎖兩個(gè)要求

要求一:分布式鎖的加鎖和釋放鎖的過程,涉及多個(gè)操作筐喳。所以催式,在實(shí)現(xiàn)分布式鎖時(shí),我們需要保證這些鎖操作的原子性避归;

要求二:共享存儲(chǔ)系統(tǒng)保存了鎖變量荣月,如果共享存儲(chǔ)系統(tǒng)發(fā)生故障或宕機(jī),那么客戶端也就無法進(jìn)行鎖操作了梳毙。在實(shí)現(xiàn)分布式鎖時(shí)哺窄,我們需要考慮保證共享存儲(chǔ)系統(tǒng)的可靠性,進(jìn)而保證鎖的可靠性账锹。

3萌业、基于單個(gè) Redis 節(jié)點(diǎn)實(shí)現(xiàn)分布式鎖

作為分布式鎖實(shí)現(xiàn)過程中的共享存儲(chǔ)系統(tǒng),Redis 可以使用鍵值對(duì)來保存鎖變量奸柬,再接收和處理不同客戶端發(fā)送的加鎖和釋放鎖的操作請(qǐng)求生年。

要賦予鎖變量一個(gè)變量名,把這個(gè)變量名作為鍵值對(duì)的鍵廓奕,而鎖變量的值抱婉,則是鍵值對(duì)的值,這樣一來桌粉,Redis 就能保存鎖變量了蒸绩,客戶端也就可以通過 Redis 的命令操作來實(shí)現(xiàn)鎖操作。??

1)铃肯、加鎖過程

加鎖過程

2)患亿、解鎖過程

解鎖過程

因?yàn)?b>加鎖包含了三個(gè)操作(讀取鎖變量、判斷鎖變量值以及把鎖變量值設(shè)置為 1)缘薛,而這三個(gè)操作在執(zhí)行時(shí)需要保證原子性窍育,要想保證操作的原子性,有兩種通用的方法宴胧,分別是使用 Redis 的單命令操作和使用 Lua 腳本漱抓。

3)、Redis 單命令加鎖:

首先是 SETNX 命令恕齐,它用于設(shè)置鍵值對(duì)的值乞娄。這個(gè)命令在執(zhí)行時(shí)會(huì)判斷鍵值對(duì)是否存在如果不存在显歧,就設(shè)置鍵值對(duì)的值仪或,如果存在,就不做任何設(shè)置士骤。

SETNX key value

對(duì)于釋放鎖操作來說范删,我們可以在執(zhí)行完業(yè)務(wù)邏輯后,使用 DEL 命令刪除鎖變量拷肌。

4)到旦、總結(jié)來說,我們就可以用 SETNX 和 DEL 命令組合來實(shí)現(xiàn)加鎖和釋放鎖操作:

// 加鎖

SETNX lock_key 1

// 業(yè)務(wù)邏輯

DO THINGS

// 釋放鎖

DEL lock_key

5)巨缘、使用 SETNX 和 DEL 命令組合實(shí)現(xiàn)分布鎖添忘,存在兩個(gè)潛在的風(fēng)險(xiǎn):

A、某個(gè)客戶端在執(zhí)行了 SETNX 命令若锁、加鎖之后搁骑,緊接著卻在操作共享數(shù)據(jù)時(shí)發(fā)生了異常,結(jié)果一直沒有執(zhí)行最后的 DEL 命令釋放鎖又固。

因此仲器,鎖就一直被這個(gè)客戶端持有,其它客戶端無法拿到鎖仰冠,也無法訪問共享數(shù)據(jù)和執(zhí)行后續(xù)操作娄周,這會(huì)給業(yè)務(wù)應(yīng)用帶來影響

解決:給鎖變量設(shè)置一個(gè)過期時(shí)間

B沪停、客戶端 A 執(zhí)行了 SETNX 命令加鎖后煤辨,假設(shè)客戶端 B 執(zhí)行了 DEL 命令釋放鎖,此時(shí)木张,客戶端 A 的鎖就被誤釋放了众辨。

如果客戶端 C 正好也在申請(qǐng)加鎖,就可以成功獲得鎖舷礼,進(jìn)而開始操作共享數(shù)據(jù)鹃彻。這樣一來,客戶端 A 和 C 同時(shí)在對(duì)共享數(shù)據(jù)進(jìn)行操作妻献,數(shù)據(jù)就會(huì)被修改錯(cuò)誤蛛株,這也是業(yè)務(wù)層不能接受的团赁。

解決:能區(qū)分來自不同客戶端的鎖操作

在加鎖操作時(shí),可以讓每個(gè)客戶端給鎖變量設(shè)置一個(gè)唯一值谨履,這里的唯一值就可以用來標(biāo)識(shí)當(dāng)前操作的客戶端欢摄。在釋放鎖操作時(shí)客戶端需要判斷笋粟,當(dāng)前鎖變量的值是否和自己的唯一標(biāo)識(shí)相等怀挠,只有在相等的情況下,才能釋放鎖害捕。這樣一來绿淋,就不會(huì)出現(xiàn)誤釋放鎖的問題了。

6)尝盼、Redis具體怎么實(shí)現(xiàn)分布式鎖的

首先 set 命令:

SETNX 命令的時(shí)候提到吞滞,對(duì)于不存在的鍵值對(duì),它會(huì)先創(chuàng)建再設(shè)置值(也就是“不存在即設(shè)置”)盾沫,為了能達(dá)到和 SETNX 命令一樣的效果冯吓,Redis 給 SET 命令提供了類似的選項(xiàng) NX,用來實(shí)現(xiàn)“不存在即設(shè)置”疮跑。

如果使用了 NX 選項(xiàng)组贺,SET 命令只有在鍵值對(duì)不存在時(shí),才會(huì)進(jìn)行設(shè)置祖娘,否則不做賦值操作失尖。

SET 命令在執(zhí)行時(shí)還可以帶上 EX 或 PX 選項(xiàng),用來設(shè)置鍵值對(duì)的過期時(shí)間渐苏。

執(zhí)行下面的命令時(shí)掀潮,只有 key 不存在時(shí),SET 才會(huì)創(chuàng)建 key琼富,并對(duì) key 進(jìn)行賦值仪吧。key 的存活時(shí)間由 seconds 或者 milliseconds 選項(xiàng)值來決定

SET key value [EX seconds | PX milliseconds] [NX]

有了 SET 命令的 NX 和 EX/PX 選項(xiàng)后鞠眉,就可以用下面的命令來實(shí)現(xiàn)加鎖操作:

// 加鎖, unique_value作為客戶端唯一性的標(biāo)識(shí)

SET lock_key unique_value NX PX 10000

unique_value 是客戶端的唯一標(biāo)識(shí)薯鼠,可以用一個(gè)隨機(jī)生成的字符串來表示,PX 10000 則表示 lock_key 會(huì)在 10s 后過期械蹋,以免客戶端在這期間發(fā)生異常而無法釋放鎖出皇。

因?yàn)樵诩渔i操作中,每個(gè)客戶端都使用了一個(gè)唯一標(biāo)識(shí)(一般是線程ID)哗戈,所以在釋放鎖操作時(shí)郊艘,我們需要判斷鎖變量的值,是否等于執(zhí)行釋放鎖操作的客戶端的唯一標(biāo)識(shí),如下所示:

//釋放鎖 比較unique_value是否相等纱注,避免誤釋放

if redis.call("get",KEYS[1]) == ARGV[1] then

? ? return redis.call("del",KEYS[1])

else

? ? return 0

end

這是使用 Lua 腳本(unlock.script)實(shí)現(xiàn)的釋放鎖操作的偽代碼畏浆,其中,KEYS[1]表示 lock_key狞贱,ARGV[1]是當(dāng)前客戶端的唯一標(biāo)識(shí)刻获,這兩個(gè)值都是我們在執(zhí)行 Lua 腳本時(shí)作為參數(shù)傳入的。

最后斥滤,執(zhí)行下面的命令,完成鎖釋放操作:

redis-cli --eval unlock.script lock_key , unique_value

釋放鎖操作中勉盅,使用了 Lua 腳本佑颇,這是因?yàn)椋?b>釋放鎖操作的邏輯也包含了讀取鎖變量、判斷值草娜、刪除鎖變量的多個(gè)操作挑胸,而 Redis 在執(zhí)行 Lua 腳本時(shí),可以以原子性的方式執(zhí)行炎功,從而保證了鎖釋放操作的原子性漆腌。

4钉汗、基于多個(gè) Redis 節(jié)點(diǎn)實(shí)現(xiàn)高可靠的分布式鎖

要實(shí)現(xiàn)高可靠的分布式鎖時(shí),就不能只依賴單個(gè)的命令操作了解藻,我們需要按照一定的步驟和規(guī)則進(jìn)行加解鎖操作,否則葡盗,就可能會(huì)出現(xiàn)鎖無法工作的情況螟左。

分布式鎖算法 Redlock:Redlock 算法的實(shí)現(xiàn)需要有 N 個(gè)獨(dú)立的 Redis 實(shí)例。

Redlock 算法的基本思路觅够,是讓客戶端和多個(gè)獨(dú)立的 Redis 實(shí)例依次請(qǐng)求加鎖胶背,如果客戶端能夠和半數(shù)以上的實(shí)例成功地完成加鎖操作,那么我們就認(rèn)為喘先,客戶端成功地獲得分布式鎖了钳吟,否則加鎖失敗。

這樣一來窘拯,即使有單個(gè) Redis 實(shí)例發(fā)生故障红且,因?yàn)殒i變量在其它實(shí)例上也有保存,所以涤姊,客戶端仍然可以正常地進(jìn)行鎖操作直焙,鎖變量并不會(huì)丟失。

Redlock?三步完成加鎖操作:

第一步是砂轻,客戶端獲取當(dāng)前時(shí)間奔誓。

第二步是,客戶端按順序依次向 N 個(gè) Redis 實(shí)例執(zhí)行加鎖操作。

這里的加鎖操作和在單實(shí)例上執(zhí)行的加鎖操作一樣厨喂,使用 SET 命令和措,帶上 NX,EX/PX 選項(xiàng)蜕煌,以及帶上客戶端的唯一標(biāo)識(shí)派阱。當(dāng)然,如果某個(gè) Redis 實(shí)例發(fā)生故障了斜纪,為了保證在這種情況下贫母,Redlock 算法能夠繼續(xù)運(yùn)行,我們需要給加鎖操作設(shè)置一個(gè)超時(shí)時(shí)間盒刚。

如果客戶端在和一個(gè) Redis 實(shí)例請(qǐng)求加鎖時(shí)腺劣,一直到超時(shí)都沒有成功,那么此時(shí)因块,客戶端會(huì)和下一個(gè) Redis 實(shí)例繼續(xù)請(qǐng)求加鎖橘原。加鎖操作的超時(shí)時(shí)間需要遠(yuǎn)遠(yuǎn)地小于鎖的有效時(shí)間,一般也就是設(shè)置為幾十毫秒涡上。

第三步是趾断,一旦客戶端完成了和所有 Redis 實(shí)例的加鎖操作,客戶端就要計(jì)算整個(gè)加鎖過程的總耗時(shí)吩愧。

客戶端只有在滿足下面的這兩個(gè)條件時(shí)芋酌,才能認(rèn)為是加鎖成功。

條件一:客戶端從超過半數(shù)(大于等于 N/2+1)的 Redis 實(shí)例上成功獲取到了鎖雁佳;

條件二:客戶端獲取鎖的總耗時(shí)沒有超過鎖的有效時(shí)間隔嫡。

在滿足了這兩個(gè)條件后,我們需要重新計(jì)算這把鎖的有效時(shí)間甘穿,計(jì)算的結(jié)果是鎖的最初有效時(shí)間減去客戶端為獲取鎖的總耗時(shí)腮恩。

如果鎖的有效時(shí)間已經(jīng)來不及完成共享數(shù)據(jù)的操作了,我們可以釋放鎖温兼,以免出現(xiàn)還沒完成數(shù)據(jù)操作秸滴,鎖就過期了的情況。

如果沒能滿足上面兩個(gè)條件募判,客戶端向所有 Redis 節(jié)點(diǎn)發(fā)起釋放鎖的操作荡含。

Redlock 算法中,釋放鎖的操作和在單實(shí)例上釋放鎖的操作一樣届垫,只要執(zhí)行釋放鎖的 Lua 腳本就可以了释液。只要 N 個(gè) Redis 實(shí)例中的半數(shù)以上實(shí)例能正常工作,就能保證分布式鎖的正常工作

實(shí)際的業(yè)務(wù)應(yīng)用中装处,想要提升分布式鎖的可靠性误债,就可以通過 Redlock 算法來實(shí)現(xiàn)

5、小結(jié)

A寝蹈、分布式鎖是由共享存儲(chǔ)系統(tǒng)維護(hù)的變量李命,多個(gè)客戶端可以向共享存儲(chǔ)系統(tǒng)發(fā)送命令進(jìn)行加鎖或釋放鎖操作。Redis 作為一個(gè)共享存儲(chǔ)系統(tǒng)箫老,可以用來實(shí)現(xiàn)分布式鎖封字。

B、在基于單個(gè) Redis 實(shí)例實(shí)現(xiàn)分布式鎖時(shí)耍鬓,對(duì)于加鎖操作阔籽,我們需要滿足三個(gè)條件。

1)牲蜀、加鎖包括了讀取鎖變量笆制、檢查鎖變量值和設(shè)置鎖變量值三個(gè)操作,但需要以原子操作的方式完成各薇,所以项贺,我們使用 SET 命令帶上 NX 選項(xiàng)來實(shí)現(xiàn)加鎖君躺;

2)峭判、鎖變量需要設(shè)置過期時(shí)間,以免客戶端拿到鎖后發(fā)生異常棕叫,導(dǎo)致鎖一直無法釋放林螃,所以,在 SET 命令執(zhí)行時(shí)加上 EX/PX 選項(xiàng)俺泣,設(shè)置其過期時(shí)間疗认;

3)、鎖變量的值需要能區(qū)分來自不同客戶端的加鎖操作伏钠,以免在釋放鎖時(shí)横漏,出現(xiàn)誤釋放操作,所以熟掂,使用 SET 命令設(shè)置鎖變量值時(shí)缎浇,每個(gè)客戶端設(shè)置的值是一個(gè)唯一值用于標(biāo)識(shí)客戶端赴肚。

和加鎖類似素跺,釋放鎖也包含了讀取鎖變量值、判斷鎖變量值和刪除鎖變量三個(gè)操作誉券,不過指厌,無法使用單個(gè)命令來實(shí)現(xiàn),所以踊跟,采用 Lua 腳本執(zhí)行釋放鎖操作踩验,通過 Redis 原子性地執(zhí)行 Lua 腳本,來保證釋放鎖操作的原子性

C晰甚、基于單個(gè) Redis 實(shí)例實(shí)現(xiàn)分布式鎖時(shí)衙传,會(huì)面臨實(shí)例異常或崩潰的情況厕九,這會(huì)導(dǎo)致實(shí)例無法提供鎖操作蓖捶,正因?yàn)榇耍?b>Redis 也提供了 Redlock 算法,用來實(shí)現(xiàn)基于多個(gè)實(shí)例的分布式鎖扁远。

這樣一來俊鱼,鎖變量由多個(gè)實(shí)例維護(hù),即使有實(shí)例發(fā)生了故障畅买,鎖變量仍然是存在的并闲,客戶端還是可以完成鎖操作。Redlock 算法是實(shí)現(xiàn)高可靠分布式鎖的一種有效解決方案谷羞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帝火,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子湃缎,更是在濱河造成了極大的恐慌犀填,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗓违,死亡現(xiàn)場離奇詭異九巡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蹂季,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門冕广,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人偿洁,你說我怎么就攤上這事撒汉。” “怎么了涕滋?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵睬辐,是天一觀的道長。 經(jīng)常有香客問我何吝,道長溉委,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任爱榕,我火速辦了婚禮瓣喊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黔酥。我一直安慰自己藻三,他們只是感情好洪橘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棵帽,像睡著了一般熄求。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逗概,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天弟晚,我揣著相機(jī)與錄音,去河邊找鬼逾苫。 笑死卿城,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铅搓。 我是一名探鬼主播瑟押,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼星掰!你這毒婦竟也來了多望?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤氢烘,失蹤者是張志新(化名)和其女友劉穎怀偷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體威始,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枢纠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年像街,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了黎棠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镰绎,死狀恐怖脓斩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情畴栖,我是刑警寧澤随静,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站吗讶,受9級(jí)特大地震影響燎猛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜照皆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一重绷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膜毁,春花似錦昭卓、人聲如沸愤钾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽能颁。三九已至,卻和暖如春倒淫,著一層夾襖步出監(jiān)牢的瞬間伙菊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工敌土, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留占业,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓纯赎,卻偏偏與公主長得像谦疾,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子犬金,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容