分布式鎖的基本概念
鎖:為了實(shí)現(xiàn)“同一時(shí)間,只能有一個(gè)實(shí)例對共享資源進(jìn)行訪問”
分布式鎖:當(dāng)多個(gè)分布在不同的機(jī)器上的進(jìn)程競爭共享資源時(shí)亏钩,就無法使用單機(jī)鎖實(shí)現(xiàn)同步烟零。此時(shí)就需要使用分布式鎖宁炫,而分布式鎖通常需要保存(或者說依賴于)第三方,常使用的第三方有 mysql龙亲,redis 和 zookeeper。
這里重點(diǎn)整理基于 zookeeper 的分布式鎖■基于 zookeeper 的實(shí)現(xiàn)也有兩種形式
1) 臨時(shí)節(jié)點(diǎn)
● 獲取鎖:多個(gè)實(shí)例去競爭創(chuàng)建同一個(gè)臨時(shí)節(jié)點(diǎn)杜耙,因?yàn)橥粋€(gè)臨時(shí)節(jié)點(diǎn)只可能存在一個(gè),因此只會有一個(gè)實(shí)例創(chuàng)建成功迎膜,而創(chuàng)建成功的實(shí)例視為獲取鎖成功泥技;其他的實(shí)例則監(jiān)聽該節(jié)點(diǎn)的變化
● 釋放鎖:利用臨時(shí)節(jié)點(diǎn)「會話中斷,節(jié)點(diǎn)刪除」的特點(diǎn)磕仅,實(shí)現(xiàn)鎖的釋放珊豹。
2) 臨時(shí)順序節(jié)點(diǎn)
● 獲取鎖:讓多個(gè)實(shí)例各自創(chuàng)建一個(gè) 臨時(shí)順序節(jié)點(diǎn),每次讓順序號最小的節(jié)點(diǎn)的實(shí)例持有分布式鎖榕订。讓其他實(shí)例監(jiān)聽最后一個(gè)序號比它小的節(jié)點(diǎn)店茶。
● 釋放鎖:同樣,利用臨時(shí)節(jié)點(diǎn)「會話中斷劫恒,節(jié)點(diǎn)刪除」的特點(diǎn)贩幻,實(shí)現(xiàn)鎖的釋放。
其中两嘴,基于 zookeeper 實(shí)現(xiàn)的分布式鎖有如下特點(diǎn):
zk本身是強(qiáng)一致性丛楚,非常試合作為分布式鎖
實(shí)現(xiàn)簡單,且有現(xiàn)成的監(jiān)聽通知機(jī)制憔辫,就避免不斷輪詢鎖的狀態(tài)
臨時(shí)節(jié)點(diǎn)的特性「會話中斷趣些,節(jié)點(diǎn)刪除」,使得鎖的釋放更簡單
第一種實(shí)現(xiàn)方法存在一個(gè)問題「驚群」贰您,即由于大量實(shí)例監(jiān)聽一個(gè)節(jié)點(diǎn)坏平,當(dāng)該節(jié)點(diǎn)刪除時(shí),所有實(shí)例都會得到響應(yīng)锦亦,這種現(xiàn)狀非常不好舶替。因此存在大量實(shí)例競爭鎖時(shí),應(yīng)該采用第二種方案更好 [1]
實(shí)現(xiàn)Demo
實(shí)現(xiàn)依賴的開源包:
demo 功能描述:
● 啟動(dòng)三個(gè)實(shí)例競爭分布式鎖杠园,
● 當(dāng)獲得鎖的實(shí)例會訪問共享資源顾瞪,訪問完后會釋放鎖,并重新參與競爭
demo 實(shí)現(xiàn)代碼如下:
package main
import (
"fmt"
"time"
"github.com/docker/libkv"
"github.com/docker/leadership"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/zookeeper"
)
func init() {
// step1:對使用的接口進(jìn)行注冊抛蚁,相當(dāng)于注冊驅(qū)動(dòng)
// 如果不注冊一下陈醒,步驟3會失敗
zookeeper.Register()
}
func main(){
// step2:zookeeper相關(guān)信息配置
// zookeeper的ip:port
zkHost := []string {"127.0.0.1:2181"}
// 每個(gè)實(shí)例將會在這個(gè)路徑下創(chuàng)建臨時(shí)順序節(jié)點(diǎn)
path := "qconf/backup/zk_local"
// step3:Create a store using pkg/store.
client, err := libkv.NewStore("zk", zkHost, &store.Config{})
if err != nil {
panic(err)
}
// step4:創(chuàng)建一個(gè)競爭分布式鎖的候選者
// 參數(shù)一:之前創(chuàng)建的storeClient
// 參數(shù)二:競爭分布式鎖的路徑
// 參數(shù)三:自我標(biāo)識
// 參數(shù)四:超時(shí)時(shí)間
candidate := leadership.NewCandidate(client, path, "underwood", 15*time.Second)
// step5:執(zhí)行競爭分布式鎖
// 返回的第一個(gè)通道:競爭結(jié)果
// 返回的第二個(gè)通道:競爭時(shí)產(chǎn)生的錯(cuò)誤
electedCh, errCh := candidate.RunForElection()
for{
// step6:阻塞等待結(jié)果
var err error
select{
case err = <-errCh: // 競爭過程出現(xiàn)出錯(cuò)
fmt.Printf("err=%v\n", err)
break
case isElected := <- electedCh: // 競爭結(jié)果出來了
if isElected{ // 競爭成功,即獲得鎖篮绿。
fmt.Println("i am the leader")
time.Sleep(20*time.Second) // 模擬訪問共享數(shù)據(jù)
fmt.Println("i give up leadership")
candidate.Resign() // 釋放鎖孵延,然后重新參加競爭
}else{ // 競爭失敗,即未獲得鎖亲配。有一種特殊情況尘应,任何實(shí)例競爭前都會將自己聲明為follower
fmt.Println("i am the follower")
}
}
if err != nil{
break
}
}
}
測試結(jié)果
-
第一個(gè)啟動(dòng)的實(shí)例
-
第二個(gè)啟動(dòng)的實(shí)例
-
第三個(gè)啟動(dòng)的實(shí)例
PS:截取了部分結(jié)果惶凝,未截取部分才是截取部分的循環(huán)
底層源碼分析
底層實(shí)現(xiàn)原理:基于 zookeeper 的臨時(shí)順序節(jié)點(diǎn),實(shí)現(xiàn)分布式鎖犬钢。
● 順序號最小的節(jié)點(diǎn)所連接的實(shí)例苍鲜,獲得鎖
● 未獲得鎖的實(shí)例將監(jiān)聽“順序號比它小一號的節(jié)點(diǎn)的狀態(tài)”
競選邏輯:candidate.RunForElection()
上面代碼最關(guān)鍵的在于 “l(fā)ock.Lock()”。由于本次 demo NewStore() 使用的是 zookeeper玷犹,因此 lock 對象是 zookeeperLock 實(shí)例對象混滔。所以,接下來要看 “zookeeperLock.Lock()”實(shí)現(xiàn)歹颓。主要的步驟包括(具體實(shí)現(xiàn)見下方代碼注釋)
- 創(chuàng)建臨時(shí)順序節(jié)點(diǎn)
- 找出所有臨時(shí)順序節(jié)點(diǎn)的最小順序號以及當(dāng)前實(shí)例所連接的臨時(shí)順序節(jié)點(diǎn)的前一個(gè)順序號
- 如果當(dāng)前實(shí)例的順序號是最小坯屿,就獲得鎖;反之巍扛,阻塞等待
- 當(dāng)阻塞結(jié)束领跛,重新判斷一遍,以防止前一節(jié)點(diǎn)只是單純掉線導(dǎo)致節(jié)點(diǎn)刪除了
參考:
[1] Zookeeper實(shí)現(xiàn)分布式鎖 - 簡書