看大數(shù)據(jù)大佬手把手帶你實現(xiàn)基于Zookeeper、Redis的分布式鎖
在分布式系統(tǒng)中,分布式鎖是為了解決多實例之間的同步問題。例如master選舉,能夠獲取分布式鎖的就是master,獲取失敗的就是slave。又或者能夠獲取鎖的實例能夠完成特定的操作。
目前比較常用的分布式鎖實現(xiàn)有兩種,基于zookeeper實現(xiàn)和基于redis實現(xiàn)。zookeeper和redis也是生產(chǎn)環(huán)境中經(jīng)常用到的第三方組件。下面我會分析它們的實現(xiàn)原理。
分布式鎖實現(xiàn)要求
實現(xiàn)一個分布式鎖至少要滿足下面三點要求:
- 互斥,在任何時候同一個鎖只能由一個客戶端持有。
- 不會死鎖,就算持有的客戶端異常崩潰也不會影響后續(xù)客戶端加鎖。
- 誰加鎖誰解鎖,加鎖和解鎖都必須是同一個客戶端。
zookeeper分布式鎖
在講解zookeeper的分布式鎖之前有兩個概念需要明確:
- 臨時節(jié)點:生命周期和鏈接周期一致。例如客戶端鏈接A創(chuàng)建了臨時節(jié)點NodeA,如果鏈接A關(guān)閉或者網(wǎng)絡(luò)異常斷開,那么NodeA也會跟著消失。
- 順序節(jié)點:節(jié)點名稱按照順序從小到大創(chuàng)建,例如先創(chuàng)建了000000001,那么接著創(chuàng)建的節(jié)點就會分配000000002。
zookeeper的分布式鎖實現(xiàn)原理就是利用臨時順序節(jié)點,大概流程為:
- 每個客戶端對某個功能加鎖時,在zookeeper指定目錄下生成一個唯一的臨時順序節(jié)點。
- 所有臨時節(jié)點中序號最小的節(jié)點即為當(dāng)前鎖的持有者。
- 釋放鎖時將自己持有的臨時節(jié)點刪除即可。
例如,對于加鎖過程,所有的客戶端都在/lock目錄下面創(chuàng)建臨時節(jié)點,如果發(fā)現(xiàn)自己創(chuàng)建的臨時節(jié)點是/lock目錄中最小的節(jié)點,那么就獲取鎖成功,否則就watch比自己小的節(jié)點中的最大節(jié)點。
監(jiān)控比自己小的節(jié)點中的最大節(jié)點是為了避免“驚群”效應(yīng),避免一個鎖釋放把所有等待的客戶端喚醒,但是只有一個客戶端能獲取鎖。
對于釋放鎖,只需要把自己創(chuàng)建的臨時順序節(jié)點刪除即可。整個過程流程圖如下:

優(yōu)點:鎖安全性高,zookeeper數(shù)據(jù)不易丟失。用戶使用簡單。
缺點:性能消耗比較高。因為需要動態(tài)產(chǎn)生和刪除臨時節(jié)點,當(dāng)集群負(fù)載比較高時臨時節(jié)點消失會有時間差(一般在一分鐘范圍內(nèi))。
redis分布式鎖
redis的分布式鎖實現(xiàn)比zookeeper分布式鎖實現(xiàn)復(fù)雜,也分為redis單實例和多實例(master-master)實現(xiàn)方式。
需要特別指出的是redis如果是master-slave這種結(jié)構(gòu)部署時,獲取和釋放鎖都只能向master請求,和單實例的實現(xiàn)原理基本一樣,否則主從切換時會出現(xiàn)多人拿到同一把鎖的情況。
例如:
客戶端A在master拿到了鎖。
master節(jié)點在把A創(chuàng)建的key寫入slave之前宕機了。(主從同步是異步操作)
slave變成了master節(jié)點。
B也得到了和A還持有的相同的鎖,因為slave還沒有A持有鎖的信息。
redis單實例實現(xiàn)方案
通過下面命令獲得鎖:
SET resource_name my_random_value NX PX 30000
這個命令的作用是只有這個key不存在時才會設(shè)置這個key的值(NX的作用,即not exist),超時時間設(shè)置為30000毫秒(PX的作用),這個key的值設(shè)置為my_random_value。這個值必須在所有獲取鎖請求的客戶端里面保持唯一。
key值的超時時間,也叫做“鎖有效時間”。這是鎖的自動釋放時間。
這套實現(xiàn)方案在非分布式的、單點的、保證永不宕機的環(huán)境是適用的。
redis集群實現(xiàn)方案(Redlock算法)
在分布式版本的算法里我們假設(shè)有N個redis master節(jié)點,這些節(jié)點完全獨立,不用任何的復(fù)制或者分布式協(xié)調(diào)算法來同步數(shù)據(jù)。
這里假設(shè)N=5,一個客戶端獲取鎖的過程如下:
- 獲取當(dāng)前以毫秒為單位的時間。
- 輪詢用相同的key在N個節(jié)點上面請求鎖。(每個請求的超時時間設(shè)置的短一些,為了一個master節(jié)點不用時,快速請求下一個master)。
- 如果在超過一半master節(jié)點上面成功獲取鎖(這里是3個),客戶端計算第二步請求鎖花費的時間,如果小于鎖釋放時間,則認(rèn)為獲取鎖成功。
- 如果鎖獲取成功了,那么現(xiàn)在 鎖自動釋放時間=最初鎖釋放時間-請求鎖花費的時間
- 如果獲取鎖失敗了(成功的鎖不超過master數(shù)量的一般 或者 請求耗時>鎖釋放時間),那么客戶端都會在每個master節(jié)點上面釋放鎖。
獲取鎖成功的節(jié)點數(shù)需要超過master節(jié)點數(shù)量的一半才認(rèn)為是獲取鎖成功的思路應(yīng)該是借鑒了zookeeper的paxos算法。
還有一個需要指出的點是,當(dāng)一個客戶端獲取失敗時應(yīng)該隨時延時后再進(jìn)行重試,避免多個客戶端同時重試又同時失敗。
優(yōu)點:性能高
缺點:單實例會有單點問題,多實例主從切換會導(dǎo)致數(shù)據(jù)丟失,master-master集群模式實現(xiàn)復(fù)雜。
看大佬給你講解基于Zookeeper、Redis的分布式鎖

