分布式鎖上-初探
1.分布式鎖產生的背景
開發人員很多時候會遇到在一個JVM實例內,多個線程要競爭使用具有排它性的共享資源,恰好JDK中提供了如synchronized 、 JUC包中的xxxLock 這些鎖工具,使用它們可以方便我們實現對臨界資源的互斥使用;使用鎖是一種顯式同步的方式,來控制線程間操作發生相對順序的機制,除此之外其實還有另外一種隱式同步方式,即消息傳遞,但大家似乎對顯式使用鎖的方式更為熟悉一些。
現在的系統大多已演進為分布式的架構,如實例內多線程要互斥使用臨界資源,分布式情況下多實例之間也存在需要排它的使用共享資源,這個問題就是分布式互斥問題。雖然大佬Lamport 在論文 《Time, clocks, and the ordering of events in a distributed system》中早就證明了使用狀態機(如共識算法)就能夠去中心化解決多進程互斥問題,但我們還是會更偏向使用分布式鎖,原因大概有以下3點:
- 使用分布式鎖服務更易于保持現有程序的結構
- 程序員更熟悉用加鎖的方式來同步訪問資源
- 共識算法可能會要求客戶端運行在更多的服務器上以滿足共識算法要求,而實際只滿足客戶端功能性的需求卻不需要使用這么多服務器
概括來說,分布式鎖更輕便、更易用、也更節省資源。
2.使用分布式鎖的目的
使用分布式鎖的目的主要有兩種:
- 效率(Efficiency):通過鎖來避免多次做重復的工作,計算重復的內容等等。這種場景下即便偶然出現多個用戶同時持有鎖,并同時與資源服務發生交互,也是可以忍受的。
- 正確性(Correctness):也就是“安全”,我們希望資源服務在鎖的保護下能夠做“正確”的事。更嚴謹的說,我們希望任一時刻,只有一個用戶能夠訪問資源服務,而且即便鎖在該用戶與資源服務交互的中途過期,也不至于破壞資源服務的一致性。
3.分布式鎖的功能特點
一個分布式鎖應具備這樣一些功能特點:
- 互斥性:在同一時刻,只有一個客戶端能持有鎖
- 安全性:避免死鎖,如果某個客戶端獲得鎖之后處理時間超過最大約定時間,或者持鎖期間發生了故障導致無法主動釋放鎖,其持有的鎖也能夠被其他機制正確釋放,并保證后續其它客戶端也能加鎖,整個處理流程繼續正常執行。
- 可用性:也被稱作容錯性,分布式鎖需要有高可用能力,避免單點故障,當提供鎖的服務節點故障(宕機)時不影響服務運行,這里有兩種模式:一種是分布式鎖服務自身具備集群模式,遇到故障能自動切換恢復工作;另一種是客戶端向多個獨立的鎖服務發起請求,當某個鎖服務故障時仍然可以從其他鎖服務讀取到鎖信息(Redlock)
- 可重入性:對同一個鎖,加鎖和解鎖必須是同一個進程,即不能把其他進程持有的鎖給釋放了。
- 高效靈活:加鎖、解鎖的速度要快;支持阻塞和非阻塞;支持公平鎖和非公平鎖。
4.多視角下的分布式鎖
通過不同視角,可以從多維度充分了解各種分布式鎖的實現差異
視角1:
- 輪詢類:基于數據庫和基于 Redis 實現的分布式鎖,這類實現需要客戶端不停反復請求鎖服務以查看是否能夠獲取到鎖;
- 監聽類:基于 ZooKeeper 或 etcd 實現的分布式鎖,這類實現客戶端只需監聽(watch) 某個 key,當鎖可用時鎖服務會通知客戶端,無需客戶端不停請求鎖服務。
視角2:
基于數據庫實現的分布式鎖:使用樂觀鎖、悲觀鎖或基于主鍵唯一約束來實現
基于分布式緩存實現分布式鎖:Redis 和基于 Redis 的 RedLock(Redisson)
基于分布式一致性算法實現的分布式鎖:ZooKeeper、 Etcd
視角3:
Martin Kleppmann 在個人博客中發了一篇文章 How to do distributed locking,其中涉及了大量對 Redlock 算法安全性的質疑,Salvatore Sanfilippo(Redis 的創始人,也是這里 Redlock 算法的作者)隨后發表 Is Redlock safe? 回應這些質疑;這兩位大佬的對決一定要圍觀。
本文轉載自微信公眾號「架構染色」,可以通過以下二維碼關注。轉載本文請聯系【架構染色】公眾號作者。