淺談ZooKeeper分散式鎖(來自LJ)
目前的應用系統,大部分都是分散式部署的,分散式應用中的資料一致性問題是每個系統都要面臨的問題,分散式的CAP理論告訴我們“一個分散式系統不可能同時滿足一致性(C:Consistency)、可用性(A:Availability)和分割槽容錯性(P:Partition tolerance)這三個基本需求,最多隻能同時滿足兩項。”,所以在對一個分散式系統架構設計的過程中,往往會在系統的可用性和資料一致性之前進行反覆的權衡,在很多場景中我們犧牲了強一致性,選擇最終一致性,來達到系統高可用的目標。所謂最終一致性,其實就是降低了標準的一致性,即以”資料一致性存在延遲時間”來換取資料讀寫的高效能,目前最終一致性基本成為越來越多的分散式系統所遵循的一個設計目標。
現在有很多技術方案可以支援最終一致性,比如基於事務型訊息佇列的最終一致性,基於訊息佇列加定時補償機制的最終一致性,業務系統業務邏輯的commit/rollback機制,非同步回撥機制,業務應用系統的冪等性控制等等,本文主要探討冪等性控制解決方案中的分散式鎖,所謂分散式鎖就是分散式應用各節點對共享資源的排他式訪問而設定的鎖,用來實現多節點的最終一致性。目前比較常用的有以下幾種方案:資料庫實現的分散式鎖,快取redis實現的分散式鎖,ZooKeeper實現的分散式鎖,本文主要探討基於ZooKeeper實現的分散式鎖。
1.主要知識點
1.1 ZooKeeper的節點
在ZooKeeper中,每個資料節點都是有生命週期的,其生命週期的長短,取決於資料節點的節點型別,節點型別可以分為持久節點(PERSISTENT )、臨時節點(EPHEMERAL),和順序節點(SEQUENTIAL )三大類,其中,持久節點是指一旦這個ZNode被建立,除非主動進行ZNode的移除操作,否則這個ZNode將一直儲存在ZooKeeper上。而臨時節點就不一樣了,它的生命週期和客戶端會話繫結,一旦客戶端會話失效,那麼這個客戶端的所有臨時節點都會被移除。 具體在節點建立過程中,通過組合使用,可以生成 4 種節點型別:持久節點(PERSISTENT),持久順序節點(PERSISTENT_SEQUENTIAL),臨時節點(EPHEMERAL),臨時有序節點(EPHEMERAL_SEQUENTIAL)。
1.2 ZooKeeper的Watcher機制
這裡特別強調一下,ZooKeeper的Watcher機制:一個Watcher事件是一個一次性的觸發器,當被設定了Watch的資料發生了改變的時,伺服器將這個改變傳送給設定了Watcher的客戶端,接著會將這個Watcher從session中刪除,因此,如果想繼續監控,必須重新註冊。當前ZooKeeper有如下四種Watch事件:1)節點建立;2)節點刪除;3)節點資料修改;4)子節點變更。
2.主要步驟
1.每當程序需要訪問共享資源時,客戶端呼叫create()方法建立名為“disLock/lock”的節點,需要注意的是,這裡節點的建立型別需要設定為臨時有序節點。 2.在建立子節點後,客戶端呼叫getChildren(“disLock”)方法來獲取所有已經建立的子節點,注意此時不用設定任何Watcher。 3.如果發現自己在步驟1中建立的節點序號最小,那麼就認為這個客戶端獲得了鎖。 4.假如不是序號最小的節點,就獲得該節點的上一順序節點,並給該節點是否存在註冊監聽事件。同時在這裡阻塞,等待監聽事件的發生,獲得鎖控制權。 5.當呼叫完共享資源後,刪除被關注的臨時節點,進而可以引發監聽事件,釋放該鎖。
3.主要優點
1.ZooKeeper分散式鎖使用臨時節點可以有效的解決鎖無法釋放的問題,一旦客戶端獲取到鎖之後突然掛掉(Session連線斷開),那麼這個臨時節點就會自動刪除掉。其他客戶端就可以再次獲得鎖。 2.ZooKeeper分散式鎖支援Watcher機制,這樣實現阻塞鎖,可以watch鎖資料,等到資料被刪除,ZooKeeper會通知客戶端去重新競爭鎖。 3.ZooKeeper可以有效的解決單點問題,ZooKeeper是叢集部署的,只要叢集中有半數以上的機器存活,就可以對外提供服務。
4.分散式鎖實踐
可以直接使用ZooKeeper第三方庫Curator客戶端,Curator提供了可重入鎖Shared Reentrant Lock,不可重入鎖Shared Lock,可重入讀寫鎖Shred Reentrant Read Write Lock,訊號量Shared Semaphore,多鎖物件 Multi Shared Lock。 本文主要描述最常用的可重入鎖Shared Reentrant Lock,是由Curator提的InterProcessMutex實現的,下面我們來看一下InterProcessMutex的用法。
4.1建立
1 2 3 4 5 |
publicInterProcessMutex(CuratorFramework client,Stringpath) { this(client,path,newStandardLockInternalsDriver()); } |
client是ZooKeeper客戶端鏈,path是加鎖的ZooKeeper節點path。 InterProcessMutex例項是一個可重用的物件。不需要每次使用時建立一個新的例項,可以安全的使用單例。你可以在一個執行緒中多次呼叫acquire,線上程擁有鎖時它總是返回true,具體實現原理可以檢視原始碼。
4.2 使用
4.21 獲得鎖 如果想要獲得鎖,則需要在下列acquire方法中,選擇一個呼叫: (1) 永久阻塞
1 2 3 4 5 6 7 8 |
publicvoidacquire()throwsException { if(!internalLock(-1,null)) { thrownewIOException("Lost connection while trying to acquire lock: "+basePath); } } |
此方法,會申請獲得一個互斥鎖。並且會一直阻塞,直到獲得可用鎖為止。 注意:同一個已持有鎖的執行緒可以直接重入的。每一此鎖用完後都應該釋放,也即是每一個acquire方法都應該有一個對應的release方法。
(2) 限期阻塞
1 2 3 4 5 |
publicbooleanacquire(longtime,TimeUnit unit)throwsException { returninternalLock(time,unit); } |
和上面的方法類似。區別在於,此方法不會永久阻塞,而是在超過等待期限後,返回是否獲得鎖的結果。
4.22 釋放鎖
1 2 |
publicvoidrelease() |
持有鎖的執行緒如果呼叫此方法,就會釋放持有的鎖。但是,要注意,如果執行緒多次呼叫了acquire方法,那麼也應該呼叫相同次數的 release方法,否則執行緒會繼續持有鎖。
4.23 撤銷 InterProcessMutex支援一種協商撤銷互斥鎖的機制,可以用於死鎖的情況,想要撤銷一個互斥鎖可以呼叫下面這個方法:
1 2 3 4 5 |
publicvoidmakeRevocable(RevocationListener listener) { makeRevocable(listener,MoreExecutors.sameThreadExecutor()); } |
這個方法可以讓鎖持有者來處理撤銷動作。 當其他程序/執行緒想要你釋放鎖時,就會回撥引數中的監聽器方法。 但是,此方法不是強制撤銷 的,是一種協商機制。 當想要去撤銷/釋放一個鎖時,可以通過Revoker中的靜態方法來發出請求:
1 2 3 4 5 6 7 8 9 10 11 12 |
publicstaticvoidattemptRevoke(CuratorFramework client,Stringpath)throwsException { try { client.setData().forPath(path,LockInternals.REVOKE_MESSAGE); } catch(KeeperException.NoNodeException ignore) { // ignore } } |
path是加鎖節點,通常可以用InterProcessMutex.getParticipantNodes()獲得。 這個方法會發出撤銷某個鎖的請求。如果鎖的持有者註冊了上述的RevocationListener監聽器,那麼就會呼叫監聽器方法協商撤銷鎖。
4.24 錯誤處理 如下方法:
1 2 3 4 5 6 7 8 9 |
voidregisterListeners(CuratorFramework client){ lient.getConnectionStateListenable().addListener(newConnectionStateListener(){ @Override publicvoidstateChanged(CuratorFramework client,ConnectionState newState){ //處理邏輯 } }); } |
推薦使用ConnectionStateListener用以處理連線異常的情況。當ZooKeeper連線狀態的變化出現異常時, 將通過ConnectionStateListener 介面進行監聽, 並進行相應的處理, 這些異常狀態變化包括: (1)暫停(SUSPENDED): 當連線丟失, 將暫停所有操作, 直到連線重新建立, 如果在規定時間內無法建立連線, 將觸發LOST通知。 (2)重連(RECONNECTED): 連線丟失, 執行重連時, 將觸發該通知 (3)丟失(LOST): 連線超時時, 將觸發該通知。 如果遇到連結中斷SUSPENDE,在恢復連結RECONNECTED之前,就不能保證是不是還持有鎖了,而如果連結丟失LOST,那就意味著不再 持有鎖了。
5.總結
在分散式系統中,共享資源互斥訪問問題非常普遍,而針對訪問共享資源的互斥問題,常用的解決方案就是使用分散式鎖,使用ZooKeeper實現分散式鎖可以有效有效的解決單點問題,不可重入問題,非阻塞問題以及鎖無法釋放的問題,實現起來較為簡單。但是使用ZooKeeper實現的分散式鎖需要對ZooKeeper的原理有所瞭解。