Go 中分佈式鎖學習筆記
【導讀】本文介紹了分佈式鎖和基於 etcd 的分佈式鎖實現。
分佈式鎖
分佈式鎖的特點
-
互斥性:和我們本地鎖一樣互斥性是最基本的,但是分佈式鎖需要保證在不同節點的不同線程的互斥。
-
可重入性:同一個節點上的同一個線程如果獲取鎖之後那麼也可以再次獲取這個鎖。
-
鎖超時:和本地鎖一樣支持鎖超時,防止死鎖。
-
高效,高可用:加鎖和解鎖需要高效,同時也需要保證高可用防止分佈式鎖失效,可以增加降級。
-
支持阻塞和非阻塞:和 ReentrantLock 一樣支持 lock 和 trylock 以及 tryLock(long timeOut)。
-
支持公平鎖和非公平鎖 (可選):公平鎖的意思是按照請求加鎖的順序獲得鎖,非公平鎖就相反是無序的。這個一般來說實現的比較少。
分佈式鎖的實現
-
MySQL
-
通過隔離性,唯一索引
-
排他鎖
-
樂觀鎖
-
ZoomKeeper
-
利用順序臨時節點的特性來實現
-
Redis
-
Setnx()
-
自研分佈式鎖,Chubby
鎖的可靠性
爲了確保分佈式鎖可用,我們至少要確保鎖的可靠性,要滿足以下四個條件:
-
互斥性,在任意時刻,只能有一個客戶端(或者說業務請求)獲得鎖,並且也只能由該客戶端請求解鎖成功
-
避免死鎖,即使獲取了鎖的客戶端崩潰沒有釋放鎖,也要保證鎖正常過期,後續的客戶端能正常加鎖
-
容錯性,只要大部分 Redis 節點可用,客戶端就能正常加鎖
-
自旋重試,獲取不到鎖時,不要直接返回失敗,而是支持一定的週期自旋重試,設置一個總的超時時間,當過了超時時間以後還沒有獲取到鎖則返回失敗
代碼實例
加鎖的目的就是爲了防止競態。
Q:代碼中有j.l = newChanMutex()
,在 +646L 有 j.locker.TryLock()
,這兩個 lock(l 和 locker) 有何區別?
分佈式鎖具有互斥性,也就是 locker 是在 l 上實現的?
我們在 baseJob 中可以看到相關的定義,如
// baseJob中的Struct裏有如下定義
// 互斥鎖,用來實現讀寫互斥。當某線程無法獲取互斥鎖時,該線程會被直接掛起,不再消耗CPU時間,當其他線程釋放互斥鎖後,操作系統會喚醒那個被掛起的線程
l *chanMutex
// etcd分佈式鎖,爲分佈式應用各節點對共享資源的排他式訪問而設定的鎖,主要目的是爲了保障節點的最終一致性
locker etcd.Locker
// chanMutex是一個結構體,作爲接收者,間接實現了Lock/Unlock/TryLock
type chanMutex struct {
ch chan struct{}
}
// Locker是一個接口,直接實現了Lock/Unlock/TryLock
type Locker interface {
// Lock acquire lock in etcd
// it will be blocked if the lock is acquired by other locker with the same key
Lock() error
// TryLock try to acquire lock
// it will return immediately if the lock is already acquired
TryLock() (bool, error)
Unlock() error
// SetExpiration will stop refreshing and reset ttl with expiration
SetExpiration(time.Time) error
// StopRefresh make sure refresh() exists
StopRefresh()
}
chanMutex 作爲接收者實現的 Lock 相關方法是通過通道實現的,而 Locker 中的相關方法就是正常實現的,etcdLock 作爲上述方法的接收者
type etcdLock struct {
manager *Manager
kapi client.KeysAPI
ctx context.Context
cancel context.CancelFunc
acquireCtx context.Context
acquireCancel context.CancelFunc
refreshCtx context.Context
refreshCancel context.CancelFunc
refreshStopC chan struct{}
locked bool
key string
value string
ttl time.Duration
timeout time.Duration
refreshEnabled bool
refreshing bool
refreshInterval time.Duration
acceptPrevValue bool
backoff Backoff
}
我們看下 newChanMutex 的實現
func newChanMutex() *chanMutex {
m := &chanMutex{
ch: make(chan struct{}, 1),
}
m.ch <- struct{}{}
return m
}
Q: JobManager 中也有一個 lock 和 mLock,二者有何區別?
JobManager struct 的定義如下
type JobManager struct {
mode string
lock sync.Mutex
m *models.DemonModel
em *external.Manager
etm *etcd.Manager
o orm.Ormer
registeredJobs map[string]Job
c *cron.Cron
host *models.Host
refreshTicker *time.Ticker
mLock sync.RWMutex
}
其中,lock 是原生的互斥鎖,mLock 是原生的讀寫互斥鎖。RWMutex 是在 Mutex 基礎上實現的。
Mutex 和 RWMutex 的定義如下
// Mutex可理解爲全局鎖,只允許一個讀或者寫的情況
type Mutex struct {
state int32
sema uint32
}
// RWMutex可以加多個讀鎖和一個寫鎖,用於讀次數遠大於寫次數的場景
type RWMutex struct {
w Mutex // held if there are pending writers
writerSem uint32 // semaphore for writers to wait for completing readers
readerSem uint32 // semaphore for readers to wait for completing writers
readerCount int32 // number of pending readers
readerWait int32 // number of departing readers
}
我們看看在哪裏調用了 lock 和 mLock
func (manager *JobManager) registerJob(jobs ...Job) {
manager.lock.Lock()
defer manager.lock.Unlock()
if manager.registeredJobs == nil {
manager.registeredJobs = make(map[string]Job)
}
//....
}
可以看到在這個代碼塊,我們用到了 lock,也就是在註冊 job 的時候。
func (manager *JobManager) model() *models.DemonModel {
manager.mLock.RLock()
defer manager.mLock.RUnlock()
return manager.m
}
func (manager *JobManager) newModel() error {
manager.mLock.Lock()
defer manager.mLock.Unlock()
m, err := models.NewModel()
if err != nil {
return errors.Trace(err)
}
manager.m = m
manager.em = m.EM()
manager.etm = m.ETM()
manager.o = m.O()
manager.host = m.Host()
return nil
}
而在和數據庫打交道的時候,我們會用到 mLock。
結合這兩個鎖的使用場景,我們可以發現二者的區別在於他們的使用場景不同,lock 適用於讀寫場景單一的情況,而 mLock 適用於讀多寫少的情況下。另一方面,lock 即原生的互斥鎖對其他的讀寫操作都會進行阻塞,但事實上我們並不需要阻塞讀,所以有了 mLock,也就是 RWMutex,該讀寫鎖不會對讀進行阻塞。
分佈式鎖設計的要點
-
鎖的時效,應避免單點故障造成死鎖
-
可重入鎖:需要我們在關鍵節點處進行檢查。
-
減少獲取鎖的操作
-
加鎖的事物或者操作儘量粒度小,減少其他客戶端申請鎖的等待時間
-
持鎖客戶端解鎖後要能通知到其他等待鎖的節點
-
考慮可能出現的其他異常,不能因爲一個節點解鎖失敗而影響整個等待任務隊列
-
服務器宕機或者網絡異常要有其他備份方案,以此保證最終一致性
轉自:
juejin.cn/post/6943492133860933663
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/NcrwPJ-RrZOxIRjGhRt6Lw