為了應對大流量,現代應用/中間件通常採用分散式部署,此時不得不考慮 CAP 問題。ZooKeeper(後文簡稱 ZK)是面向 CP 設計的一個開源的分散式協調框架,將那些複雜且容易出錯的分散式一致性服務封裝起來,構成一個高效可靠的原語集,並以一系列簡單易用的介面提供給用戶使用,分散式應用程式可以基於... ...
文章分三部分展開陳述:ZooKeeper 核心知識、ZooKeeper 的典型應用實現原理、ZooKeeper 在中間件的落地案例。
為了應對大流量,現代應用/中間件通常採用分散式部署,此時不得不考慮 CAP 問題。ZooKeeper(後文簡稱 ZK)是面向 CP 設計的一個開源的分散式協調框架,將那些複雜且容易出錯的分散式一致性服務封裝起來,構成一個高效可靠的原語集,並以一系列簡單易用的介面提供給用戶使用,分散式應用程式可以基於它實現諸如 數據發佈/訂閱、負載均衡、命名服務、集群管理、Master 選舉、分散式鎖、分散式隊列 等功能。ZK 之所以能夠提供上述一套分散式數據一致性解決方案,核心在於其設計精妙的數據結構、watcher 機制、Zab 一致性協議等,下麵將依次剖析。
數據結構
ZK 在記憶體中維護了一個類似文件系統的樹狀數據結構實現命名空間(如下),樹中的節點稱為 znode。
然而,znode 要比文件系統的路徑複雜,既可以通過路徑訪問,又可以存儲數據。znode 具有四個屬性 data、acl、stat、children,如下
public class DataNode implements Record { byte data[]; Long acl; public StatPersisted stat; private Set<String> children = null; }
- data: znode 相關的業務數據均存儲在這裡,但是,父節點不可存儲數據;
- children: 存儲當前節點的子節點引用信息,因為記憶體限制,所以 znode 的子節點數不是無限的;
- stat: 包含 znode 節點的狀態信息,比如: 事務 id、版本號、時間戳等,其中事務 id 和 ZK 的數據一直性、選主相關,下麵將重點介紹;
- acl: 記錄客戶端對 znode 節點的訪問許可權;
註意:znode 的數據操作具有原子性,讀操作將獲取與節點相關的所有數據,寫操作也將替換掉節點的所有數據。znode 可存儲的最大數據量是 1MB ,但實際上我們在 znode 的數據量應該儘可能小,因為數據過大會導致 zk 的性能明顯下降。每個 ZNode 都對應一個唯一的路徑。
事物 ID:Zxid
Zxid 由 Leader 節點生成。當有新寫入事件時,Leader 節點生成新的 Zxid,並隨提案一起廣播。Zxid 的生成規則如下:
- epoch:任期/紀元,Zxid 的高 32 位, ZAB 協議通過 epoch 編號來區分 Leader 周期變化,每次一個 leader 被選出來,它都會有一個新的 epoch=(原來的 epoch+1),標識當前屬於那個 leader 的 統治時期;可以假設 leader 就像皇帝,epoch 則相當於年號,每個皇帝都有自己的年號;
- 事務計數器:Zxid 的低 32 位,每次數據變更,計數器都會加一;
zxid 是遞增的,所以誰的 zxid 越大,就表示誰的數據是最新的。每個節點都保存了當前最近一次事務的 Zxid。Zxid 對於 ZK 的數據一致性以及選主都有著重要意義,後邊在介紹相關知識時會重點講解其作用原理。
znode 類型
節點根據生命周期的不同可以將劃分為持久節點和臨時節點。持久節點的存活時間不依賴於客戶端會話,只有客戶端在顯式執行刪除節點操作時,節點才消失;臨時節點的存活時間依賴於客戶端會話,當會話結束,臨時節點將會被自動刪除(當然也可以手動刪除臨時節點)。註意:臨時節點不能擁有子節點。
節點類型是在創建時進行制定,後續不能改變。如create /n1 node1
創建了一個數據為”node1”的持久節點/n1;在上述指令基礎上加上參數-e:create -e /n1/n3 node3
,則創建了一個數據為”node3”的臨時節點 /n1/n3。
create 命令還有一個可選參數-s 用於指定創建的節點是否具有順序特性。創建順序節點時,zk 會在路徑後面自動追加一個 遞增的序列號 ,這個序列號可以保證在同一個父節點下是唯一的,利用該特性我們可以實現分散式鎖 等功能。
基於 znode 的上述兩組特性,兩兩組合後可構建 4 種類型的節點:
- PERSISTENT:永久節點
- EPHEMERAL:臨時節點
- PERSISTENT_SEQUENTIAL:永久順序節點
- EPHEMERAL_SEQUENTIAL:臨時順序節點
Watcher 監聽機制
Watcher 監聽機制是 ZK 非常重要的一個特性。ZK 允許 Client 端在指定節點上註冊 Watcher,監聽節點數據變更、節點刪除、子節點狀態變更等事件,當特定事件發生時,ZK 服務端會非同步通知註冊了相應 Watcher 的客戶端,通過該機制,我們可以利用 ZK 實現數據的發佈和訂閱等功能。
Watcher 監聽機制由三部分協作完成:ZK 服務端、ZK 客戶端、客戶端的 WatchManager 對象。工作時,客戶端首先將 Watcher 註冊到服務端,同時將 Watcher 對象保存到客戶端的 Watch 管理器中。當 ZK 服務端監聽的數據狀態發生變化時,服務端會主動通知客戶端,接著客戶端的 Watch 管理器會觸發相關 Watcher 來回調相應處理邏輯。
註意:
- watcher 變更通知是一次性的:當數據發生變化的時候, ZK 會產生一個 watcher 事件,並且會發送到客戶端。但是客戶端只會收到一次通知。如果後續這個節點再次發生變化,那麼之前設置 Watcher 的客戶端不會再次收到消息。可以通過迴圈監聽去達到永久監聽效果。
- 客戶端 watcher 順序回調:watcher 回調是順序串列化執行的,只有回調後客戶端才能看到節點最新的狀態。watcher 回調邏輯不應太複雜,否則可能影響 watcher 執行。
- 不會告訴節點變化前後的具體內容:watchEvent 是最小的通信單元,結構上包含通知狀態、事件類型和節點路徑,但是,不會告訴節點變化前後的具體內容。
- 時效性:watcher 只有在當前 session 徹底失效時才會無效,若在 session 有效期內快速重連成功,則 watcher 依然存在,仍可收到事件通知。
ZK 集群
為了確保服務的高可用性,ZK 採用集群化部署,如下:
ZK 集群伺服器有三種角色:Leader、Follower 和 Observer
- Leader:一個 ZK 集群同一時間只會有一個實際工作的 Leader,它會發起並維護與各 Follwer 及 Observer 間的心跳。所有的寫操作必須要通過 Leader 完成再由 Leader 將寫操作廣播給其它伺服器。
- Follower:一個 ZK 集群可同時存在多個 Follower,它會響應 Leader 的心跳。Follower 可直接處理並返回客戶端的讀請求,同時會將寫請求轉發給 Leader 處理,參與事務請求 Proposal 的投票及 Leader 選舉投票。
- Observer:Observer 是 3.3.0 版本開始引入的一個伺服器角色,一個 ZK 集群可同時存在多個 Observer, 功能與 Follower 類似,但是,不參與投票。
“早期的 ZooKeeper 集群服務運行過程中,只有 Leader 伺服器和 Follow 伺服器。隨著集群規模擴大,follower 變多,ZK 在創建節點和選主等事務性請求時,需要一半以上節點 AC,所以導致性能下降寫入操作越來越耗時,follower 之間通信越來越耗時。為瞭解決這個問題,就引入了觀察者,可以處理讀,但是不參與投票。既保證了集群的擴展性,又避免過多伺服器參與投票導致的集群處理請求能力下降。”
ZK 集群中通常有很多伺服器,那麼如何區分不同的伺服器的角色呢?可以通過伺服器的狀態進行區分
- LOOKING:尋找 Leader 狀態。當伺服器處於該狀態時,它會認為當前集群中沒有 Leader,因此需要進入 Leader 選舉狀態。
- LEADING:領導者狀態。表明當前伺服器角色是 Leader。
- FOLLOWING:跟隨者狀態,同步 leader 狀態,參與投票。表明當前伺服器角色是 Follower。
- OBSERVING:觀察者狀態,同步 leader 狀態,不參與投票。表明當前伺服器角色是 Observer。
ZK 集群是一主多從的結構,所有的所有的寫操作必須要通過 Leader 完成,Follower 可直接處理並返回客戶端的讀請求。那麼如何保證從 Follower 伺服器讀取的數據與 Leader 寫入的數據的一致性呢?Leader 萬一由於某些原因崩潰了,如何選出新的 Leader,如何保證數據恢復?Leader 是怎麼選出來的?
Zab 一致性協議
ZK 專門設計了 ZAB 協議(Zookeeper Atomic Broadcast)來保證主從節點數據的一致性。下麵分別從 client 向 Leader 和 Follower 寫數據場景展開陳述。
寫 Leader 場景數據一致性
- 客戶端向 Leader 發起寫請求
- Leader 將寫請求以 Proposal 的形式發給所有 Follower 並等待 ACK
- Follower 收到 Leader 的 Proposal 後返回 ACK
- Leader 得到過半數的 ACK(Leader 對自己預設有一個 ACK)後向所有的 Follower 和 Observer 發送 Commmit
- Leader 將處理結果返回給客戶端
註意:
- Leader 不需要得到所有 Follower 的 ACK,只要收到過半的 ACK 即可,同時 Leader 本身對自己有一個 ACK。上圖中有 4 個 Follower,只需其中兩個返回 ACK 即可,因為(2+1) / (4+1) > 1/2
- Observer 雖然無投票權,但仍須同步 Leader 的數據從而在處理讀請求時可以返回儘可能新的數據
寫 Follower 場景數據一致性
1.客戶端向 Follower 發起寫請求, Follower 將寫請求轉發給 Leader 處理;
- 其它流程與直接寫 Leader 無任何區別
註意:Observer 與 Follower 寫流程相同
最終一致性
Zab 協議消息廣播使用兩階段提交的方式,達到主從數據的最終一致性。為什麼是最終一致性呢?從上文可知數據寫入過程核心分成下麵兩階段:
- 第一階段:Leader 數據寫入事件作為提案廣播給所有 Follower 結點;可以寫入的 Follower 結點返回確認信息 ACK。
- 第二階段:Leader 收到一半以上的 ACK 信息後確認寫入可以生效,向所有結點廣播 COMMIT 將提案生效。
根據寫入過程的兩階段的描述,可以知道 ZooKeeper 保證的是最終一致性,即 Leader 向客戶端返回寫入成功後,可能有部分 Follower 還沒有寫入最新的數據,所以是最終一致性。ZooKeeper 保證的最終一致性也叫順序一致性,即每個結點的數據都是嚴格按事務的發起順序生效的。ZooKeeper 集群的寫入是由 Leader 結點協調的,真實場景下寫入會有一定的併發量,那 Zab 協議的兩階段提交是如何保證事務嚴格按順序生效的呢?ZK 事物的順序性是藉助上文中的 Zxid 實現的。Leader 在收到半數以上 ACK 後會將提案生效並廣播給所有 Follower 結點,Leader 為了保證提案按 ZXID 順序生效,使用了一個 ConcurrentHashMap,記錄所有未提交的提案,命名為 outstandingProposals,key 為 ZXID,Value 為提案的信息。對 outstandingProposals 的訪問邏輯如下:
- Leader 每發起一個提案,會將提案的 ZXID 和內容放到 outstandingProposals 中,作為待提交的提案;
- Leader 收到 Follower 的 ACK 信息後,根據 ACK 中的 ZXID 從 outstandingProposals 中找到對應的提案,對 ACK 計數;
- 執行 tryToCommit 嘗試將提案提交:判斷流程是,先判斷當前 ZXID 之前是否還有未提交提案,如果有,當前提案暫時不能提交;再判斷提案是否收到半數以上 ACK,如果達到半數則可以提交;如果可以提交,將當前 ZXID 從 outstandingProposals 中清除並向 Followers 廣播提交當前提案;
Leader 是如何判斷當前 ZXID 之前是否還有未提交提案的呢?由於前提是保證順序提交的,所以 Leader 只需判斷 outstandingProposals 里,當前 ZXID 的前一個 ZXID 是否存在。代碼如下:
所以 ZooKeeper 是通過兩階段提交保證數據的最終一致性,並且通過嚴格按照 ZXID 的順序生效提案保證其順序一致性的。
選主原理
ZK 中預設的並建議使用的 Leader 選舉演算法是:基於 TCP 的 FastLeaderElection。在分析選舉原理前,先介紹幾個重要的參數。
- 伺服器 ID(myid):每個 ZooKeeper 伺服器,都需要在數據文件夾下創建一個名為 myid 的文件,該文件包含整個 ZooKeeper 集群唯一的 ID(整數)。該參數在選舉時如果無法通過其他判斷條件選擇 Leader,那麼將該 ID 的大小來確定優先順序。
- 事務 ID(zxid):單調遞增,值越大說明數據越新,權重越大。
- 邏輯時鐘(epoch-logicalclock):同一輪投票過程中的邏輯時鐘值是相同的,每投完一次值會增加。
ZK 的 leader 選舉存在兩類,一個是伺服器啟動時 leader 選舉,另一個是運行過程中伺服器宕機時的 leader 選舉,下麵依次展開介紹。以下兩節引自從 0 到 1 詳解 ZooKeeper 的應用場景及架構 。
伺服器啟動時的 leader 選舉
1、各自推選自己:ZooKeeper 集群剛啟動時,所有伺服器的 logicClock 都為 1,zxid 都為 0。各伺服器初始化後,先把第一票投給自己並將它存入自己的票箱,同時廣播給其他伺服器。此時各自的票箱中只有自己投給自己的一票,如下圖所示:
2、更新選票:第一步中各個伺服器先投票給自己,並把投給自己的結果廣播給集群中的其他伺服器,這一步其他伺服器接收到廣播後開始更新選票操作,以 Server1 為例流程如下:
(1)Server1 收到 Server2 和 Server3 的廣播選票後,由於 logicClock 和 zxid 都相等,此時就比較 myid;
(2)Server1 收到的兩張選票中 Server3 的 myid 最大,此時 Server1 判斷應該遵從 Server3 的投票決定,將自己的票改投給 Server3。接下來 Server1 先清空自己的票箱(票箱中有第一步中投給自己的選票),然後將自己的新投票(1->3)和接收到的 Server3 的(3->3)投票一起存入自己的票箱,再把自己的新投票決定(1->3)廣播出去,此時 Server1 的票箱中有兩票:(1->3),(3->3);
(3)同理,Server2 收到 Server3 的選票後也將自己的選票更新為(2->3)並存入票箱然後廣播。此時 Server2 票箱內的選票為(2->3),(3->3);
(4)Server3 根據上述規則,無須更新選票,自身的票箱內選票仍為(3->3);
(5)Server1 與 Server2 重新投給 Server3 的選票廣播出去後,由於三個伺服器最新選票都相同,最後三者的票箱內都包含三張投給伺服器 3 的選票。
3、根據選票確定角色:根據上述選票,三個伺服器一致認為此時 Server3 應該是 Leader。因此 Server1 和 Server2 都進入 FOLLOWING 狀態,而 Server3 進入 LEADING 狀態。之後 Leader 發起並維護與 Follower 間的心跳。
運行時 Follower 重啟選舉
本節討論 Follower 節點發生故障重啟或網路產生分區恢復後如何進行選舉。
1、Follower 重啟投票給自己:Follower 重啟,或者發生網路分區後找不到 Leader,會進入 LOOKING 狀態併發起新的一輪投票。
2、發現已有 Leader 後成為 Follower:Server3 收到 Server1 的投票後,將自己的狀態 LEADING 以及選票返回給 Server1。Server2 收到 Server1 的投票後,將自己的狀態 FOLLOWING 及選票返回給 Server1。此時 Server1 知道 Server3 是 Leader,並且通過 Server2 與 Server3 的選票可以確定 Server3 確實得到了超過半數的選票。因此伺服器 1 進入 FOLLOWING 狀態。
運行時 Leader 重啟選舉
Follower 發起新投票:Leader(Server3)宕機後,Follower(Server1 和 2)發現 Leader 不工作了,因此進入 LOOKING 狀態併發起新的一輪投票,並且都將票投給自己,同時將投票結果廣播給對方。
2、更新選票:(1)Server1 和 2 根據外部投票確定是否要更新自身的選票,這裡跟之前的選票 PK 流程一樣,比較的優先順序為:logicLock > zxid > myid,這裡 Server1 的參數(L=3, M=1, Z=11)和 Server2 的參數(L=3, M=2, Z=10),logicLock 相等,zxid 伺服器 1 大於伺服器 2,因此伺服器 2 就清空已有票箱,將(1->1)和(2->1)兩票存入票箱,同時將自己的新投票廣播出去 (2)伺服器 1 收到 2 的投票後,也將自己的票箱更新。
3、重新選出 Leader:此時由於只剩兩台伺服器,伺服器 1 投票給自己,伺服器 2 投票給 1,所以 1 當選為新 Leader。
4、舊 Leader 恢複發起選舉:之前宕機的舊 Leader 恢復正常後,進入 LOOKING 狀態併發起新一輪領導選舉,並將選票投給自己。此時伺服器 1 會將自己的 LEADING 狀態及選票返回給伺服器 3,而伺服器 2 將自己的 FOLLOWING 狀態及選票返回給伺服器 3。
5、舊 Leader 成為 Follower:伺服器 3 瞭解到 Leader 為伺服器 1,且根據選票瞭解到伺服器 1 確實得到過半伺服器的選票,因此自己進入 FOLLOWING 狀態。
腦裂
對於一主多從類的集群應用,通常要考慮腦裂問題,腦裂會導致數據不一致。那麼,什麼是腦裂?簡單點來說,就是一個集群有兩個 master。通常腦裂產生原因如下:
- 假死:由於心跳超時(網路原因導致的)認為 Leader 死了,但其實 Leader 還存活著。
- 腦裂:由於假死會發起新的 Leader 選舉,選舉出一個新的 Leader,但舊的 Leader 網路又通了,導致出現了兩個 Leader ,有的客戶端連接到老的 Leader,而有的客戶端則連接到新的 Leader。
通常解決腦裂問題有 Quorums(法定人數)方式、Redundant communications(冗餘通信)方式、仲裁、磁碟鎖等方式。ZooKeeper 採用 Quorums 這種方式來防止“腦裂”現象,只有集群中超過半數節點投票才能選舉出 Leader。
典型應用場景
數據發佈/訂閱
我們可基於 ZK 的 Watcher 監聽機制實現數據的發佈與訂閱功能。ZK 的發佈訂閱模式採用的是推拉結合的方式實現的,實現原理如下:
- 當集群中的服務啟動時,客戶端向 ZK 註冊 watcher 監聽特定節點,並從節點拉取數據獲取配置信息;
- 當發佈者變更配置時,節點數據發生變化,ZK 會發送 watcher 事件給各個客戶端;客戶端在接收到 watcher 事件後,會從該節點重新拉取數據獲取最新配置信息。
註意:Watch 具有一次性,所以當獲得伺服器通知後要再次添加 Watch 事件。
負載均衡
利用 ZK 的臨時節點、watcher 機制等特性可實現負載均衡,具體思路如下:
把 ZK 作為一個服務的註冊中心,基本流程:
- 服務提供者 server 啟動時在 ZK 進行服務註冊(創建臨時文件);
- 服務消費者 client 啟動時,請求 ZK 獲取最新的服務存活列表並註冊 watcher,然後將獲得服務列表保存到本地緩存中;
- client 請求 server 時,根據自己的負載均衡演算法,從伺服器列表選取一個進行通信。
- 若在運行過程中,服務提供者出現異常或人工關閉不能提供服務,臨時節點失效,ZK 探測到變化更新本地服務列表並非同步通知到服務消費者,服務消費者監聽到服務列表的變化,更新本地緩存
註意:服務發現可能存在延遲,因為服務提供者掛掉到緩存更新大約需要 3-5s 的時間(根據網路環境不同還需仔細測試)。為了保證服務的實時可用,client 請求 server 發生異常時,需要根據服務消費報錯信息,進行重負載均衡重試等。
命名服務
命名服務是指通過指定的名字來獲取資源或者服務的地址、提供者等信息。以 znode 的路徑為名字,znode 存儲的數據為值,可以很容易構建出一個命名服務。例如 Dubbo 使用 ZK 來作為其命名服務,如下
- 所有 Dubbo 相關的數據都組織在
/dubbo
的根節點下; - 二級目錄是服務名,如
com.foo.BarService
; - 三級目錄有兩個子節點,分別是
providers
和consumers
,表示該服務的提供者和消費者; - 四級目錄記錄了與該服務相關的每一個應用實例的 URL 信息,在
providers
下的表示該服務的所有提供者,而在consumers
下的表示該服務的所有消費者。舉例說明,com.foo.BarService
的服務提供者在啟動時將自己的 URL 信息註冊到/dubbo/com.foo.BarService/providers
下;同樣的,服務消費者將自己的信息註冊到相應的consumers
下,同時,服務消費者會訂閱其所對應的providers
節點,以便能夠感知到服務提供方地址列表的變化。
集群管理
基於 ZK 的臨時節點和 watcher 監聽機制可實現集群管理。集群管理通常指監控集群中各個主機的運行時狀態、存活狀況等信息。如下圖所示,主機向 ZK 註冊臨時節點,監控系統註冊監聽集群下的臨時節點,從而獲取集群中服務的狀態等信息。
Master 選舉
ZK 中某節點同一層子節點,名稱具有唯一性,所以,多個客戶端創建同一節點時,只會有一個客戶端成功。利用該特性,可以實現 maser 選舉,具體如下:
- 多個客戶端同時競爭創建同一臨時節點/master-election/master,最終只能有一個客戶端成功。這個成功的客戶端成為 Master,其它客戶端置為 Slave。
- Slave 客戶端都向這個臨時節點的父節點/master-election 註冊一個子節點列表的 watcher 監聽。
- 一旦原 Master 宕機,臨時節點就會消失,zk 伺服器就會向所有 Slave 發送子節點變更事件,Slave 在接收到事件後會競爭創建新的 master 臨時子節點。誰創建成功,誰就是新的 Master。
分散式鎖
基於 ZK 的臨時順序節點和 Watcher 機制可實現公平分散式鎖。下麵具體看下多客戶端獲取及釋放 zk 分散式鎖的整個流程及背後的原理。下麵過程引自七張圖徹底講清楚 ZooKeeper 分散式鎖的實現原理【石杉的架構筆記】 。
假如說客戶端 A 先發起請求,就會搞出來一個順序節點,大家看下麵的圖,Curator 框架大概會弄成如下的樣子:
這一大坨長長的名字都是 Curator 框架自己生成出來的。然後,因為客戶端 A 是第一個發起請求的,所以給他搞出來的順序節點的序號是"1"。接著客戶端 A 會查一下"my_lock"這個鎖節點下的所有子節點,並且這些子節點是按照序號排序的,這個時候大概會拿到這麼一個集合:
接著客戶端 A 會走一個關鍵性的判斷:唉!兄弟,這個集合里,我創建的那個順序節點,是不是排在第一個啊?如果是的話,那我就可以加鎖了啊!因為明明我就是第一個來創建順序節點的人,所以我就是第一個嘗試加分散式鎖的人啊!bingo!加鎖成功!大家看下麵的圖,再來直觀的感受一下整個過程。
假如說客戶端 A 加完鎖完後,客戶端 B 過來想要加鎖,這個時候它會幹一樣的事兒:先是在"my_lock"這個鎖節點下創建一個臨時順序節點,因為是第二個來創建順序節點的,所以 zk 內部會維護序號為"2"。接著客戶端 B 會走加鎖判斷邏輯,查詢"my_lock"鎖節點下的所有子節點,按序號順序排列,此時看到的類似於:
同時檢查自己創建的順序節點,是不是集合中的第一個?明顯不是,此時第一個是客戶端 A 創建的那個順序節點,序號為"01"的那個。所以加鎖失敗!加鎖失敗了以後,客戶端 B 就會通過 ZK 的 API 對他的順序節點的上一個順序節點加一個監聽器, 即對客戶端 A 創建的那個順序節加監聽器!如下
接著,客戶端 A 加鎖之後,可能處理了一些代碼邏輯,然後就會釋放鎖。那麼,釋放鎖是個什麼過程呢?
其實很簡單,就是把自己在 zk 里創建的那個順序節點,也就是:
這個節點被刪除。
刪除了那個節點之後,zk 會負責通知監聽這個節點的監聽器,也就是客戶端 B 之前加的那個監聽器,說:兄弟,你監聽的那個節點被刪除了,有人釋放了鎖。
此時客戶端 B 的監聽器感知到了上一個順序節點被刪除,也就是排在他之前的某個客戶端釋放了鎖。
此時,就會通知客戶端 B 重新嘗試去獲取鎖,也就是獲取"my_lock"節點下的子節點集合,此時為:
集合里此時只有客戶端 B 創建的唯一的一個順序節點了!
然後呢,客戶端 B 判斷自己居然是集合中的第一個順序節點,bingo!可以加鎖了!直接完成加鎖,運行後續的業務代碼即可,運行完了之後再次釋放鎖。
註意:利用 ZK 實現分散式鎖時要避免出現驚群效應。上述策略中,客戶端 B 通過監聽比其節點順序小的那個臨時節點,解決了驚群效應問題。
分散式隊列
基於 ZK 的臨時順序節點和 Watcher 機制可實現簡單的 FIFO 分散式隊列。ZK 分散式隊列和上節中的分散式鎖本質是一樣的,都是基於對上一個順序節點進行監聽實現的。具體原理如下:
- 利用順序節點的有序性,為每個數據在/FIFO 下創建一個相應的臨時子節點;且每個消費者均在/FIFO 註冊一個 watcher;
- 消費者從分散式隊列獲取數據時,首先嘗試獲取分散式鎖,獲取鎖後從/FIFO 獲取序號最小的數據,消費成功後,刪除相應節點;
- 由於消費者均監聽了父節點/FIFO,所以均會收到數據變化的非同步通知,然後重覆 2 的過程,嘗試消費隊列數據。依此迴圈,直到消費完畢。
中間件落地案例
Kafka
ZK 在 Kafka 集群中扮演著極其重要的角色。Kafka 中很多信息都在 ZK 中維護,如 broker 集群信息、consumer 集群信息、 topic 相關信息、 partition 信息等。Kafka 的很多功能也是基於 ZK 實現的,如 partition 選主、broker 集群管理、consumer 負載均衡等,限於篇幅本文將不展開陳述,這裡先附一張網上截圖大家感受下,詳情將在 Kafka 專題中細聊。
Dubbo
Dubbo 使用 Zookeeper 用於服務的註冊發現和配置管理,詳情見上文“命名服務”。
參考文獻
https://mp.weixin.qq.com/s/tiAQQXbh7Tj45_1IQmQqZg
https://www.jianshu.com/p/68b45694026c
https://time.geekbang.org/column/article/239261
https://blog.csdn.net/lihao21/article/details/51810395
https://zhuanlan.zhihu.com/p/378018463
https://juejin.cn/post/6974737393324654628
https://blog.csdn.net/liuao107329/article/details/78936160
https://blog.csdn.net/en_joker/article/details/78799737
https://blog.51cto.com/u_15077535/4199740
https://juejin.cn/post/6844903729406148622
https://blog.csdn.net/Saintmm/article/details/124110149
https://www.wumingx.com/linux/zk-kafka.html
作者:mosun本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/ZooKeeper-core-general-knowledge.html