Zookeeper基礎原理&應用場景詳解

detectiveHLH發表於2021-04-21

簡單瞭解Zookeeper

Tips: 如果之前對Zookeeper不瞭解的話,這裡大概留個印象就好了

Zookeeper是一個分散式協調服務,可以用於後設資料管理、分散式鎖、分散式協調、釋出訂閱、服務命名等等。

例如,Kafka中就是用Zookeeper來儲存其叢集中的相關後設資料,例如Broker、Topic以及Partition等等。同時,基於Zookeeper的Watch監聽機制,還可以用其實現釋出、訂閱的功能。

在平常的常規業務使用場景下,我們幾乎只會使用到分散式鎖這一個用途。

Zookeeper內部執行機制

Zookeeper的底層儲存原理,有點類似於Linux中的檔案系統。Zookeeper中的檔案系統中的每個檔案都是節點(Znode)。根據檔案之間的層級關係,Zookeeper內部就會形成這個這樣一個檔案樹。

在Linux中,檔案(節點)其實是分型別的,例如分為檔案、目錄。在Zookeeper中同理,Znode同樣的有型別。在Zookeeper中,所有的節點型別如下:

  • 持久節點(Persistent)
  • 持久順序節點(Persistent Sequential)
  • 臨時節點(Ephemeral)
  • 臨時順序節點(Ephemeral Sequential)

所謂持久節點,就和我們自己在電腦上新建一個檔案一樣,除非你主動刪除,否則一直存在。

持久順序節點除了繼承了持久節點的特性之外,還會為其下建立的子節點保證其先後順序,並且會自動地為節點加上10位自增序列號作為節點名,以此來保證節點名的唯一性。這一點上圖中的subfiles已經給出了示例。

臨時節點,其生命週期和client的連線是否活躍相關,如果client一旦斷開連線,該節點(可以理解為檔案)就都會被刪除,並且臨時節點無法建立子節點

PS:這裡的斷開連線其實不是我們直覺上理解的斷開連線,Zookeeper有其Session機制,當某個client的Session過期之後,會將對應的client建立的節點全部刪除

Zookeeper的節點建立方式

接下來我們來分別看看幾種節點的建立方式,給出幾個簡單的示例。

建立持久節點

create /node_name SH的全棧筆記 

這裡需要注意的是,命令中所有的節點名稱必須要以/開頭,否則會建立失敗,因為在Zookeeper中是不能使用相對路徑,必須要使用絕對路徑。

建立持久順序節點

create -s /node_name SH的全棧筆記

可以看到,Zookeeper為key自動的加上了10位的自增字尾。

建立臨時節點

create -e /test SH的全棧筆記

建立臨時順序節點

create -e -s /node_name SH的全棧筆記

Zookeeper的用途

我們通過一些具體的例子,來了解Zookeeper的詳細用途,它不僅僅只是被當作分散式鎖使用。

後設資料管理

我們都知道,Kafka在執行時會依賴一個Zookeeper的叢集。Kafka通過Zookeeper來管理叢集的相關後設資料,並通過Zookeeper進行Leader選舉。

Tips: 但是即將釋出的Kafka 2.8版本中,Zookeeper已經不是一個必需的元件了。這塊我暫時還沒有時間去細看,不過我估計可能會跟RocketMQ中處理的方式差不多,將其叢集的後設資料放到Kafka本身來處理。

分散式鎖

基於Zookeeper的分散式鎖其實流程很簡單。首先我們需要知道加分散式鎖的本質是什麼?

答案是建立臨時順序節點

當某個客戶端加鎖成功之後,實際上則是成功的在Zookeeper上建立了臨時順序節點。我們知道,分散式鎖能夠使同一時間只能有一個能夠訪問某種資源。那這就必然會涉及到分散式鎖的競爭,那問題來了,當前這個客戶端是如何感知搶到了鎖呢?

其實在客戶端側會有一定的邏輯,假設加鎖的key為/locks/modify_users

首先,客戶端會發起加鎖請求,然後會在Zookeeper上建立持久節點locks,然後會在該節點下建立臨時順序節點。臨時順序節點的建立示例,如下圖所示。

當客戶端成功建立了節點之後,還會獲取其同級的所有節點。也就是上圖中的所有modify_users000000000x的節點。

此時客戶端會根據10位的自增序號去判斷,當前自己建立的節點是否是所有的節點中最小的那個,如果是最小的則自己獲取到了分散式鎖

你可能會問,那如果我不是最小的怎麼辦呢?而且我的節點都已經建立了。如果不是最小的,說明當前客戶端並沒有搶到鎖。按照我們的認知,如果沒有競爭到分散式鎖,則會等待。等待的底層都做了什麼?我們用實際例子來捋一遍。

假設Zookeeper中已經有了如下的節點。

例如當前客戶端是B建立的節點是modify_users0000000002,那麼很明顯B沒有搶到鎖,因為已經有比它還要小的由客戶端A建立的節點modify_users0000000001

此時客戶端B會對節點modify_users0000000001註冊一個監聽器,對於該節點的任意更新都將觸發對應的操作。

當其被刪除之後,就會喚醒客戶端B的執行緒,此時客戶端B會再次進行判斷自己是否是序號最小的一個節點,此時modify_users0000000002明顯是最小的節點,故客戶端B加鎖成功

為了讓你更加直觀的瞭解這個過程,我把流程濃縮成了下面這幅流程圖。

分散式協調

我們都知道,在很多場景下要保證一致性都會採用經典的2PC(兩階段提交),例如MySQL中Redo Log和Binlog提交的資料一致性保障就是採用的2PC,詳情可以看基於Redo Log和Undo Log的MySQL崩潰恢復流程

在2PC中存在兩種角色,分別是參與者(Participant)協調者(Coordinator),協調者負責統一的排程所有分散式節點的執行邏輯。具體協調啥呢?舉個例子。

例如在2PC的Commit階段,兩個參與者A、B,A的commit操作成功了,但不幸的是B失敗了。此時協調者就需要向A傳送Rollback操作。Zookeeper大概就是這樣一個角色。

釋出訂閱

由於Zookeeper自帶了監聽器(Watch)的功能,所以釋出訂閱也順理成章的成為了Zookeeper的應用之一。例如在某個配置節點上註冊了監聽器,那麼該配置一旦釋出變更,對應的服務就能實時的感知到配置更改,從而達到配置的動態更新的目的。

給個簡單的Watch使用示例。

命名服務

用大白話來說,命名服務主要有兩種。

  • 單純的利用Zookeeper的檔案系統特性,儲存結構化的檔案
  • 利用檔案特性和順序節點的特性,來生成全域性的唯一標識

前者可以用於在系統之間共享某種業務上的特定資源,後者則可以用於實現分散式鎖。

好了以上就是本篇部落格的全部內容了,歡迎微信搜尋關注【SH的全棧筆記】,回覆【佇列】獲取MQ學習資料,包含基礎概念解析和RocketMQ詳細的原始碼解析,持續更新中。

如果你覺得這篇文章對你有幫助,還麻煩點個贊關個注分個享留個言

相關文章