?????? 在進行大型網站技術架構設計以及業務實現的過程中,多少都會遇到需要使用分布式鎖的情況。那么問題也就接踵而至,哪種分布式鎖更適合我們的項目?
下面就這個問題,我做了一些分析:
分布式鎖現狀:
?????? 目前幾乎很多大型網站及應用都是分布式部署的,分布式場景中的數據一致性問題一直是一個比較重要的話題。
??????? 分布式的CAP理論告訴我們“任何一個分布式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition
tolerance),最多只能同時滿足兩項。”所以,很多系統在設計之初就要對這三者做出取舍。在互聯網領域的絕大多數的場景中,都需要犧牲強一致性來換取系統的高可用性,系統往往只需要保證“最終一致性”,只要這個最終時間是在用戶可以接受的范圍內即可。
??????? 在很多場景中,我們為了保證數據的最終一致性,需要很多的技術方案來支持,比如分布式事務、分布式鎖等。有的時候,我們需要保證一個方法在同一時間內只能被同一個線程執行。在單機環境中,Java中其實提供了很多并發處理相關的API,但是這些API在分布式場景中就無能為力了。也就是說單純的Java Api并不能提供分布式鎖的能力。所以針對分布式鎖的實現目前有多種方案。
分布式鎖實現方案:
分布式鎖的實現,目前比較常用的有以下3種方案:
?1. 基于數據庫實現分布式鎖
?2. 基于緩存(redis,memcached,tair)實現分布式鎖
?3. 基于Zookeeper實現分布式鎖
在實際落地的時候 會選擇實現多個引擎(zk+redis/tair) 方便不同業務使用
分布式鎖定義:
?????? 分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。在分布式系統中,常常需要協調他們的動作。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。
分布式鎖的思考:
?????? 在分析這幾種實現方案之前我們先來想一下,我們需要的分布式鎖應該是怎么樣的?(這里以方法鎖為例,資源鎖同理)
可以保證在分布式部署的應用集群中,同一個方法在同一時間只能被一臺機器上的一個線程執行。
這把鎖要是一把可重入鎖(避免死鎖)
這把鎖最好是一把阻塞鎖(根據業務需求考慮要不要這條)
有高可用的獲取鎖和釋放鎖功能
獲取鎖和釋放鎖的性能要好
分布式鎖具體實現:
基于數據庫
? ? 簡單的方式就是建立一張鎖表,通過操作該表的數據來實現了。
? ? 這種鎖的設計是用數據庫的樂觀鎖實現的,可以滿足基本的交易的并發以及交易重試的冪等性。? ? ? 大概實現就是,根據鎖字段查找該鎖是否存在,如果存在,則判斷該鎖狀態,根據業務需要是否成功拿鎖;如果不存在,則插入鎖;
當然這種依賴數據庫實現鎖的缺陷有:
1、這把鎖強依賴數據庫的可用性,數據庫是一個單點,一旦數據庫掛掉,會導致業務系統不可用。
2、這把鎖沒有失效時間,并且鎖的數據會一直增長。
3、這把鎖只能是非阻塞的,因為數據的insert操作,一旦插入失敗就會直接報錯。沒有獲得鎖的線程并不會進入排隊隊列,要想再次獲得鎖就要再次觸發獲得鎖操作。
4、這把鎖是非重入的,同一個線程在沒有釋放鎖之前無法再次獲得該鎖。因為數據中數據已經存在了。
5、操作數據庫需要一定的開銷,性能問題需要考慮
其實針對上述我們已經對1和4做了優化:
1.數據庫做主從同步
4.為滿足可重入,設置了線程號
? ? ? ?基于數據庫排他鎖
除了可以通過增刪操作數據表中的記錄以外,其實還可以借助數據中自帶的鎖來實現分布式的鎖。
基于緩存實現分布式鎖
可以使用緩存來代替數據庫來實現分布式鎖,這個可以提供更好的性能,同時,很多緩存服務都是集群部署的,可以避免單點問題。并且很多緩存服務都提供了可以用來實現分布式鎖的方法,比如Tair的put方法,redis的setnx方法等。并且,這些緩存服務也都提供了對數據的過期自動刪除的支持,可以直接設置超時時間來控制鎖的釋放。
使用緩存實現分布式鎖的優點
性能好,實現起來較為方便。
使用緩存實現分布式鎖的缺點
通過超時時間來控制鎖的失效時間并不是十分的靠譜。
基于Zookeeper實現分布式鎖
基于zookeeper臨時有序節點可以實現的分布式鎖。
大致思想即為:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。
判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。
當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。
來看下Zookeeper能不能解決前面提到的問題。
鎖無法釋放?使用Zookeeper可以有效的解決鎖無法釋放的問題,因為在創建鎖的時候,客戶端會在ZK中創建一個臨時節點,一旦客戶端獲取到鎖之后突然掛掉(Session連接斷開),那么這個臨時節點就會自動刪除掉。其他客戶端就可以再次獲得鎖。
非阻塞鎖?使用Zookeeper可以實現阻塞的鎖,客戶端可以通過在ZK中創建順序節點,并且在節點上綁定監聽器,一旦節點有變化,Zookeeper會通知客戶端,客戶端可以檢查自己創建的節點是不是當前所有節點中序號最小的,如果是,那么自己就獲取到鎖,便可以執行業務邏輯了。
不可重入?使用Zookeeper也可以有效的解決不可重入的問題,客戶端在創建節點的時候,把當前客戶端的主機信息和線程信息直接寫入到節點中,下次想要獲取鎖的時候和當前最小的節點中的數據比對一下就可以了。如果和自己的信息一樣,那么自己直接獲取到鎖,如果不一樣就再創建一個臨時的順序節點,參與排隊。
單點問題?使用Zookeeper可以有效的解決單點問題,ZK是集群部署的,只要集群中有半數以上的機器存活,就可以對外提供服務。
可以直接使用zookeeper第三方庫??Curator?客戶端,這個客戶端中封裝了一個可重入的鎖服務。
比較三種分布式鎖
分布式鎖zk、數據庫、以及Redis三者都能實現,同樣是分布式鎖,三者的區別何在?
從理解的難易程度角度(從低到高):數據庫 > 緩存 > Zookeeper
從實現的復雜性角度(從低到高):Zookeeper >= 緩存 > 數據庫
從性能角度(從高到低):緩存 > Zookeeper >= 數據庫
從可靠性角度(從高到低):Zookeeper > 緩存 > 數據庫