新聞中心
基于 Redis 如何實(shí)現(xiàn)一個(gè)分布式鎖?
作者: 張張 2021-11-01 12:25:56
存儲(chǔ)
存儲(chǔ)軟件
分布式
Redis 與分布式鎖相對(duì)應(yīng)的是「單機(jī)鎖」,我們?cè)趯懚嗑€程程序時(shí),避免同時(shí)操作一個(gè)共享變量產(chǎn)生數(shù)據(jù)問題,通常會(huì)使用一把鎖來(lái)「互斥」,以保證共享變量的正確性,其使用范圍是在「同一個(gè)進(jìn)程」中。

站在用戶的角度思考問題,與客戶深入溝通,找到陽(yáng)谷網(wǎng)站設(shè)計(jì)與陽(yáng)谷網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名注冊(cè)、網(wǎng)絡(luò)空間、企業(yè)郵箱。業(yè)務(wù)覆蓋陽(yáng)谷地區(qū)。
一、為什么需要分布式鎖?
在開始講分布式鎖之前,有必要簡(jiǎn)單介紹一下,為什么需要分布式鎖?
與分布式鎖相對(duì)應(yīng)的是「單機(jī)鎖」,我們?cè)趯懚嗑€程程序時(shí),避免同時(shí)操作一個(gè)共享變量產(chǎn)生數(shù)據(jù)問題,通常會(huì)使用一把鎖來(lái)「互斥」,以保證共享變量的正確性,其使用范圍是在「同一個(gè)進(jìn)程」中。
如果換做是多個(gè)進(jìn)程,需要同時(shí)操作一個(gè)共享資源,如何互斥呢?
例如,現(xiàn)在的業(yè)務(wù)應(yīng)用通常都是微服務(wù)架構(gòu),這也意味著一個(gè)應(yīng)用會(huì)部署多個(gè)進(jìn)程,那這多個(gè)進(jìn)程如果需要修改 MySQL 中的同一行記錄時(shí),為了避免操作亂序?qū)е聰?shù)據(jù)錯(cuò)誤,此時(shí),我們就需要引入「分布式鎖」來(lái)解決這個(gè)問題了。
想要實(shí)現(xiàn)分布式鎖,必須借助一個(gè)外部系統(tǒng),所有進(jìn)程都去這個(gè)系統(tǒng)上申請(qǐng)「加鎖」。
而這個(gè)外部系統(tǒng),必須要實(shí)現(xiàn)「互斥」的能力,即兩個(gè)請(qǐng)求同時(shí)進(jìn)來(lái),只會(huì)給一個(gè)進(jìn)程返回成功,另一個(gè)返回失敗(或等待)。
這個(gè)外部系統(tǒng),可以是 MySQL,也可以是 Redis 或 Zookeeper。但為了追求更好的性能,我們通常會(huì)選擇使用 Redis 或 Zookeeper 來(lái)做。
下面我就以 Redis 為主線,由淺入深,帶你深度剖析一下,分布式鎖的各種「安全性」問題,幫你徹底理解分布式鎖。
二、分布式鎖怎么實(shí)現(xiàn)?
我們從最簡(jiǎn)單的開始講起。
想要實(shí)現(xiàn)分布式鎖,必須要求 Redis 有「互斥」的能力,我們可以使用 SETNX 命令,這個(gè)命令表示SET if Not eXists,即如果 key 不存在,才會(huì)設(shè)置它的值,否則什么也不做。
兩個(gè)客戶端進(jìn)程可以執(zhí)行這個(gè)命令,達(dá)到互斥,就可以實(shí)現(xiàn)一個(gè)分布式鎖。
客戶端 1 申請(qǐng)加鎖,加鎖成功:
- 127.0.0.1:6379> SETNX lock 1
- (integer) 1 // 客戶端1,加鎖成功
客戶端 2 申請(qǐng)加鎖,因?yàn)楹蟮竭_(dá),加鎖失?。?/p>
- 127.0.0.1:6379> SETNX lock 1
- (integer) 0 // 客戶端2,加鎖失敗
此時(shí),加鎖成功的客戶端,就可以去操作「共享資源」,例如,修改 MySQL 的某一行數(shù)據(jù),或者調(diào)用一個(gè) API 請(qǐng)求。
操作完成后,還要及時(shí)釋放鎖,給后來(lái)者讓出操作共享資源的機(jī)會(huì)。如何釋放鎖呢?
也很簡(jiǎn)單,直接使用 DEL 命令刪除這個(gè) key 即可:
- 127.0.0.1:6379> DEL lock // 釋放鎖
- (integer) 1
這個(gè)邏輯非常簡(jiǎn)單,整體的路程就是這樣:
但是,它存在一個(gè)很大的問題,當(dāng)客戶端 1 拿到鎖后,如果發(fā)生下面的場(chǎng)景,就會(huì)造成「死鎖」:
程序處理業(yè)務(wù)邏輯異常,沒及時(shí)釋放鎖
進(jìn)程掛了,沒機(jī)會(huì)釋放鎖
這時(shí),這個(gè)客戶端就會(huì)一直占用這個(gè)鎖,而其它客戶端就「永遠(yuǎn)」拿不到這把鎖了。
怎么解決這個(gè)問題呢?
三、如何避免死鎖?
我們很容易想到的方案是,在申請(qǐng)鎖時(shí),給這把鎖設(shè)置一個(gè)「租期」。
在 Redis 中實(shí)現(xiàn)時(shí),就是給這個(gè) key 設(shè)置一個(gè)「過期時(shí)間」。這里我們假設(shè),操作共享資源的時(shí)間不會(huì)超過 10s,那么在加鎖時(shí),給這個(gè) key 設(shè)置 10s 過期即可:
- 127.0.0.1:6379> SETNX lock 1 // 加鎖
- (integer) 1
- 127.0.0.1:6379> EXPIRE lock 10 // 10s后自動(dòng)過期
- (integer) 1
這樣一來(lái),無(wú)論客戶端是否異常,這個(gè)鎖都可以在 10s 后被「自動(dòng)釋放」,其它客戶端依舊可以拿到鎖。
疑問臉,但這樣真的沒問題嗎?
還是有問題。
現(xiàn)在的操作,加鎖、設(shè)置過期是 2 條命令,有沒有可能只執(zhí)行了第一條,第二條卻「來(lái)不及」執(zhí)行的情況發(fā)生呢?例如:
- SETNX 執(zhí)行成功,執(zhí)行 EXPIRE 時(shí)由于網(wǎng)絡(luò)問題,執(zhí)行失敗
- SETNX 執(zhí)行成功,Redis 異常宕機(jī),EXPIRE 沒有機(jī)會(huì)執(zhí)行
- SETNX 執(zhí)行成功,客戶端異常崩潰,EXPIRE 也沒有機(jī)會(huì)執(zhí)行
總之,這兩條命令不能保證是原子操作(一起成功),就有潛在的風(fēng)險(xiǎn)導(dǎo)致過期時(shí)間設(shè)置失敗,依舊發(fā)生「死鎖」問題。
那怎么辦呢?
在 Redis 2.6.12 版本之前,我們需要想盡辦法,保證 SETNX 和 EXPIRE 原子性執(zhí)行,還要考慮各種異常情況如何處理。
但在 Redis 2.6.12 之后,Redis 擴(kuò)展了 SET 命令的參數(shù),用這一條命令就可以了:
- // 一條命令保證原子性執(zhí)行
- 127.0.0.1:6379> SET lock 1 EX 10 NX
- OK
這樣就解決了死鎖問題,也比較簡(jiǎn)單。
新聞名稱:基于Redis如何實(shí)現(xiàn)一個(gè)分布式鎖?
轉(zhuǎn)載來(lái)源:http://www.5511xx.com/article/ccddcpe.html


咨詢
建站咨詢
