本文主要講解如下內容:
- 為什么要使用分布式鎖?
- 分布式鎖特性!
- 分布式鎖的實現方式有哪些?
- Curator分布式鎖原理
- Curator分布式鎖實現類UML及相關類的介紹
- 基於Redis,數據庫實現分布式鎖
為什么要使用分布式鎖?
在傳統的單機應用中,我們使用JAVA提供的synchronized、ReentrantLock、Semaphore、AtomicInteger等解決多線程並發問題,達到同步目的。單機應用所有的請求都會分配到當前服務器的JVM內部,線程間是可以共享某一個變量的。當隨着業務的發展,單機演變成集群,一個JVM擴展到多個甚至數十個JVM的時候,就不能共享同一變量了。如下圖所示:
上圖中同樣的業務部署在多台服務器上,但是又要操作同一個數據的時候,JAVA的synchronized等關鍵字也就無力回天了,這是分布式鎖正式登場。
分布式鎖特性!
1、在分布式系統環境下,一個方法在同一時間只能被一個機器的一個線程執行;
2、高可用的獲取鎖與釋放鎖;
3、高性能的獲取鎖與釋放鎖;
4、具備可重入特性;
5、具備鎖失效機制,防止死鎖;
6、具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗。
分布式鎖的實現方式有哪些?
目前幾乎很多大型網站及應用都是分布式部署的,分布式場景中的數據一致性問題一直是一個比較重要的話題。分布式的CAP理論告訴我們“任何一個分布式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多只能同時滿足兩項。”所以,很多系統在設計之初就要對這三者做出取舍。在互聯網領域的絕大多數的場景中,都需要犧牲強一致性來換取系統的高可用性,系統往往只需要保證“最終一致性”,只要這個最終時間是在用戶可以接受的范圍內即可。
在很多場景中,我們為了保證數據的最終一致性,需要很多的技術方案來支持,比如分布式事務、分布式鎖等。有的時候,我們需要保證一個方法在同一時間內只能被同一個線程執行。
基於數據庫實現分布式鎖; 基於緩存(Redis等)實現分布式鎖; 基於Zookeeper實現分布式鎖;
這三種方案之間沒有最好只有更適合,具體要看業務場景
Curator分布式鎖原理
注意:判斷自己是否是locks目錄下序號最小的節點,只會和比自己序號小的那個相鄰節點進行比較,這樣大大提高了效率。比如N節點只會和N-1幾點比較,不會和N+1,N-2節點比較。
另外zookeeper有效解決了下面的問題(重點重點重點):
鎖釋放:
使用Zookeeper可以有效的解決鎖無法釋放的問題,因為在創建鎖的時候,客戶端會在ZK中創建一個臨時節點,一旦客戶端獲取到鎖之后突然掛掉(Session連接斷開),那么這個臨時節點就會自動刪除掉。其他客戶端就可以再次獲得鎖。
阻塞鎖
使用Zookeeper可以實現阻塞的鎖,客戶端可以通過在ZK中創建順序節點,並且在節點上綁定監聽器,一旦節點有變化,Zookeeper會通知客戶端,客戶端可以檢查自己創建的節點是不是當前所有節點中序號最小的,如果是,那么自己就獲取到鎖,便可以執行業務邏輯了。
可重入鎖
使用Zookeeper也可以有效的解決不可重入的問題,客戶端在創建節點的時候,把當前客戶端的主機信息和線程信息直接寫入到節點中,下次想要獲取鎖的時候和當前最小的節點中的數據比對一下就可以了。如果和自己的信息一樣,那么自己直接獲取到鎖,如果不一樣就再創建一個臨時的順序節點,參與排隊。
單點問題
使用Zookeeper可以有效的解決單點問題,ZK是集群部署的,只要集群中有半數以上的機器存活,就可以對外提供服務。
Curator分布式鎖實現類UML及相關類的介紹
接口InterProcessLock作為神一般的存在,其它類均實現了這個接口。
看一下這個接口定義
接口中定義了四個方法:
public void acquire();//獲取鎖
public boolean acquire(long time, TimeUnit unit);//獲取鎖,可以指定阻塞時間
public void release();//釋放鎖
boolean isAcquiredInThisProcess();//判斷當前是否持有鎖,實際使用中,如果持有鎖,可以進行鎖的釋放。
實現類:
InterProcessMultiLock 將多個鎖作為單個實體管理的容器
InterProcessMutex 分布式可重入排它鎖
InterProcessReadWriteLock 分布式讀寫鎖
InterProcessSemaphoreMutex 分布式排它鎖
InterProcessSemaphoreV2 信號量
基於Redis,數據庫實現分布式鎖
基於redis和數據庫實現的分布式鎖大家可以參考以下文章: