Zookeeper入門一篇就夠了

Java成魔之路發表於2020-05-16

談點分散式

什麼是分散式呢?

起初,我們的應用流量比較小,所有東西全部部署在一個伺服器,比如全部丟給一個tomcat來處理,頂多做一個tomcat的多節點部署多分,再掛一臺Nginx做一下負載均衡就OK了。但是隨著業務功能複雜度上升,訪問流程的上升,單體架構就不行了。這個時候就該分散式上場了,將業務模組做一定拆分,各業務元件分佈在網路上不同的計算機節點上,同時為了保證高可用性和效能,單個元件模組也會做叢集部署。

分散式雖然爽了,但是隨之而來的就是分散式帶來的複雜性,比如在分散式系統中網路故障問題幾乎是必然存在的;事務也不再是資料庫幫我們保證了,因為可能每個業務有自己的庫,但不同業務之間又有保證事務的需求,這是就需要考慮實現分散式事務了;還有資料一致性問題,叢集中副本節點不能及時同步到主節點的資料,會有資料一致性問題需要解決。

分散式理論

實踐需要理論的知道,同樣地,在分散式軟體開發領域內,也是有前輩大神們做了基礎的理論研究。下面將要介紹的就是分散式相關的兩個基礎理論:CAP定理和BASE理論。

CAP定理

在聊CAP定理前,我們先簡單瞭解下分散式事務。資料庫的事務我們知道。假如銀行轉賬,轉出操作和轉入操作在同一個資料庫中,就很好實現了,只需要在方法上增加一個@Transactional,剩下的事情資料庫會幫我們做好。但是在分散式環境中,我們對比現實中的銀行轉賬,誇行轉賬,不止是誇庫,更是誇不同的銀行系統的。在這種場景,我們需要保證ACID的特性,就是需要分散式事務解決方案了。好了,這裡只是做個了結。下面說CAP定理。

CAP定理是說,在一個分散式系統中,不可能同時滿足一致性(Consistency)、可用性(Availiablity)和分割槽容錯性(Partition tolerance)這三個基本的需求。最多隻能滿足其中的兩項。

一致性

在分散式環境中,一致性是指資料在多個副本之間是否能夠保持一致的特性。在一致性的需求下,當一個系統在資料一致的狀態下執行更新操作後,應該保持系統的資料仍然處於一致的狀態。如果對第一個節點上的資料更新成功後,第二個節點上的資料並沒有得到相應的更新,那麼如果從第二個節點讀取資料,則獲取到的就是舊資料(或者或是髒資料)。這就是典型的分散式資料不一致的場景。

可用性

可用性是指系統提供的服務必須一直處於可用的狀態,對於使用者的每一個操作請求總是能在有限的時間內返回結果。這裡有限的時間就是系統響應時間

分割槽容錯性

分散式系統在遇到任何網路分割槽故障的時候,仍然需要保證能夠對外提供滿足一致性和可用性的服務,除非是整個網路環境發生了故障。

網路分割槽是指在分散式系統中,不同節點分佈在不同的子網路中,由於一些特殊的原因導致這些子網路之間出現了網路不連通的情況,但各個子網路的內部網路是正常的,從而導致整個系統的網路環境被切分成了若干個孤立的區域。

上面說到,一個分散式系統不可能同時滿足CAP的特性。但是,需要說明的是,分割槽容錯性P是一個最基本的需求。因為分散式系統中的元件必然部署在不同的網路節點上,網路問題是必然會出現的一個問題。因此就剩下兩種選擇了,即CP和AP。系統架構需要在C和A之間尋求平衡。

BASE理論

BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)的縮寫。BASE是對CAP中一致性和可用性權衡的結果。是基於CAP定理逐步演化而來,其核心思想是即使無法做到強一致性,但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終的一致性。

基本可用

分散式系統在出現不可預知的故障的時候,允許損失部分可用性。比如響應時間上的損失,原來0.2s返回的,現在可能需要2s返回。或者是部分功能上的損失,比如秒殺場景下部分使用者可能會被引導到一個降級的頁面。

軟狀態

是和硬狀態相對的,是指允許系統中的資料存在中間的狀態。但是中間的狀態並不會影響系統的整體可用性。

最終一致性

是指系統中的所有資料副本,經過一定時間的同步後,最終能夠達到一個一致的狀態。而不是要實時保證系統資料的強一致性。

Zookeeper簡介

Apache Zookeeper是由Apache Hadoop的子專案發展而來,2011年正式成為Apache的頂級專案。Zookeeper為分散式應用提供了高效且可靠的分散式協調服務。在解決分散式資料一致性方面,Zookeeper並沒有使用Paxos演算法(一種一致性演算法),而是採用了ZAB(Zookeeper Atomic Broadcast)的一致性協議。

分散式應用程式可以基於它實現諸如資料釋出/訂閱、負載均衡、命名服務、分散式協調、叢集管理、Master選舉、分散式鎖和分散式佇列等功能。Zookeeper可以保證如下分散式一致性特性。

順序一致性

同一個客戶端發起的事務請求,最終將會嚴格的按照其發起的順序被應用到Zookeeper中去。

原子性

所有事務請求的處理結果在整個叢集中所有機器上的應用情況是一致的,也就是說,也麼叢集中的所有節點都應用了一個事務,要麼都沒有應用。

單一檢視

無論客戶端連線的是哪個Zookeeper伺服器,其看到的服務端資料模型都是一致的。

可靠性

一旦服務端成功的應用了一個事務,並完成對客戶端的響應那麼該事務所引起的服務端狀態改變將會被一直保留下來,除非有另一個事務又對其進行了變更。

實時性

Zookeeper僅僅保證在一定的時間段內,客戶端最終一定能夠從服務端讀取到最新的資料狀態。

Zookeeper資料模型

Zookeeper中有資料節點的概念,我們稱之為ZNode,ZNode是Zookeeper中資料的最小單元。每個ZNode上都可以儲存資料,同時還可以掛載子節點,因此就構成了一個層次化的名稱空間,我們叫做樹。類似於Linux檔案系統中的目錄樹。

看圖就明白了。

在這裡插入圖片描述
在這裡插入圖片描述

/是根目錄,其他子節點都是從根目錄開始。類似於Linux檔案系統,不同的是Zookeeper的節點上是可以儲存資料的。

Zookeeper事務

Zookeeper中的事務,和資料庫中具有ACID特性的事務有所區別。在Zookeeper中,事務是指能夠改變Zookeeper伺服器狀態的操作,我們叫做事務操作或者更新操作,一般包括資料節點的建立和刪除、資料節點內容更新和客戶端會話建立與失效操作。對於每一個事務請求,Zookeeper都會為其分配一個全域性唯一的事務ID,用ZXID來表示,通常是一個64位的數字。每一個ZXID對應一次更新操作,從這些ZXID中可以間接地識別出Zookeeper處理這些更新操作請求的全域性順序。

資料節點的型別

在Zookeeper中,節點型別可以分為持久節點(PERSISTENT),臨時節點(EPHEMERAL)和順序節點(SEQUENTIAL)。在具體的節點建立中,通過組合,有下面四種組合節點型別:

持久節點

是最常見的一種節點型別,是指資料節點被建立後,就會一直存在於Zookeeper伺服器中,直到有刪除操作來主動刪除這個節點。

持久順序節點

和持久節點的特性是一樣的,額外的特性表現在順序性上。在Zookeeper中,每個父節點都會為它的第一級子節點維護一份順序,用於記錄每個子節點建立的順序。基於這個順序特性,在建立子節點的時候,可以設定這個標記,那麼在建立節點過程中,Zookeeper會自動為節點名加上一個數字字尾,作為一個新的完整的節點名。數字字尾的上限是整型的最大值。

臨時節點

和持久節點不同的是,臨時節點的生命週期和客戶端的會話繫結在一起,也就是說,如果客戶端的會話失效,那麼這個節點就會被自動清理掉。這裡提到的是客戶端會話失效,而非TCP連線斷開。

臨時有序節點

臨時有序節點的特性和臨時節點的特性一樣,只是增加了有序的特性。

狀態資訊

在Zookeeper客戶端中,我們通過stat命令,可以檢視節點的狀態資訊。

 1[zk: localhost:2181(CONNECTED) 6] stat /test/node1
2cZxid = 0x8
3ctime = Sun May 03 21:12:24 CST 2020
4mZxid = 0xb
5mtime = Sun May 03 21:13:08 CST 2020
6pZxid = 0x8
7cversion = 0
8dataVersion = 1
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 0

下面簡要介紹下狀態資訊中各欄位含義:

 1cZxid:  Created ZXID ,表示資料節點被建立時的事務ID。
2ctime:  Created Time,表示節點被建立的時間。
3mZxid: Modified ZXID,表示該節點最後一次被更新時的事務ID。
4mtime: Modified Time,表示該節點最後一次被更新的時間。
5pZxid: 表示該節點的子節點列表最後一次被修改時的事務ID,注意只有子節點列表變更了才會變更pZxid,子節點內容的變更不會影響pZxid。
6cversion:  子節點的版本號。
7dataVersion: 資料的版本號。
8aclVersion:  acl許可權的版本號。
9ephemeralOwner:  建立該臨時節點的會話的sessionID,如果該節點是持久節點,那麼這個屬性值為0。
10dataLength:  資料內容的長度。
11numChildren:  當前節點的子節點個數。

上面狀態中的version欄位是一種樂觀鎖機制的保證,保證併發更新資料的安全性。

Zookeeper的Watcher機制

在Zookeeper中,引入了Watcher機制來實現這種分散式的通知功能。Zookeeper允許客戶端向伺服器註冊一個Watcher監聽,當服務端的一個特定事件觸發了這個Watcher,那麼就會向客戶端傳送一個事件通知來實現分散式的通知功能。這個過程可以看下面的圖。

在這裡插入圖片描述
在這裡插入圖片描述

Zookeeper叢集節點型別

構成叢集的每一臺機器都有自己的角色,最典型的叢集模式就是Master/Slave模式。在這種叢集模式中,Master節點複雜讀寫操作,Slave負責提供讀服務,並以非同步的方式從Master同步資料。

而在Zookeeper叢集中,沒有使用傳統的Master/Slave叢集模式,而是引入了Leader、Follower和Observer三種角色。ZK叢集中的所有機器通過一個Leader選舉過程來選定一臺機器作為“Leader”的機器。Leader伺服器為客戶端提供讀和寫服務。Follower和Observer都能夠提供讀服務,唯一區別在於Observer機器不參與Leader選舉過程。

Leader

Leader伺服器是整個zk叢集工作機制中的核心,其主要工作是以下兩個:

  • 事務請求的唯一排程和處理者,保證叢集事務處理的順序性。
  • 叢集內部各伺服器的排程者。

Follower

Follower伺服器時叢集狀態的跟隨者,其主要的工作有一下三個。

  • 處理客戶端非事務請求,轉發事務請求給Leader伺服器。
  • 參與事務請求Proposal的投票。
  • 參與Leader選舉投票。

Observer

在zk叢集中充當了一個觀察者的角色,觀察叢集的最新狀態並將這些狀態變更同步過來。Observer伺服器的工作原理和Follower伺服器基本是一致的,對於非事務的請求都可以進行獨立的處理,對於事務請求會轉發給Leader處理。和Follower唯一的區別在於,Observer不參與任何形式的投票,包括事務請求Proposal投票和Leader選舉投票。

Zookeeper的客戶端操作

安裝好Zookeeper之後,就可以使用zk自帶的客戶端指令碼來進行操作了。進入Zookeeper的bin目錄之後,直接執行如下命令:

1sh zkCli.sh

當看到出現下面這句話時,說明已經成功連線到了zkserver。

1WatchedEvent state:SyncConnected type:None path:null

進入客戶端後,可以直接使用help命令看下支援哪些命令。

在這裡插入圖片描述
在這裡插入圖片描述

下面說一下在zk客戶端中怎麼操作節點和資料。

增加節點

使用create命令新建一個節點。命令格式如下:

1create [-s] [-e] [-c] [-t ttl] path [data] [acl]

-s是順序特性,-e是臨時節點。

比如執行下面命令

1[zk: localhost:2181(CONNECTED) 10] create /Java hello
2Created /Java

會在根節點下建立一個/Java節點,並且節點的資料內容是hello。預設建立的是持久節點。

可以繼續在/Java節點下建立子節點,比如:

1[zk: localhost:2181(CONNECTED) 11] create /Java/spring 123
2Created /Java/spring

讀取

使用ls命令可以看到指定節點下的所有子節點。只能看一級,不能列出子節點樹。命令格式為:

1ls [-s] [-w] [-R] path

比如執行 ls / 命令,可以看下根節點下的子節點情況。

1[zk: localhost:2181(CONNECTED) 12] ls /
2[Java, aaa, happy, test, zookeeper]

可以使用get 命令獲取節點的資料內容,命令格式為:

1get [-s] [-w] path

比如我們獲取一下/Java節點中的資料內容:

1[zk: localhost:2181(CONNECTED) 14] get /Java
2hello

獲取節點狀態資訊

使用stat命令可以獲取節點的狀態資訊,命令格式如下:

1stat [-w] path

例如,我們獲取一下/Java節點的狀態資訊

 1[zk: localhost:2181(CONNECTED) 15] stat /Java
2cZxid = 0x14
3ctime = Mon May 11 21:40:38 CST 2020
4mZxid = 0x14
5mtime = Mon May 11 21:40:38 CST 2020
6pZxid = 0x15
7cversion = 1
8dataVersion = 0
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 1

上面這些資訊我們在之前聊到Zookeeper資料節點是有講過是什麼意思,可以回顧下。

更新

使用set命令可以更新指定節點的資料內容。命令格式如下:

1 set [-s] [-v version] path data

比如我們將/Java節點的內容更新為 world。

1[zk: localhost:2181(CONNECTED) 16] set /Java world
2[zk: localhost:2181(CONNECTED) 17] get /Java
3world

資料更新完成之後,我們可以使用stat命令,再看一下節點的狀態資訊,發現dataVersion已經由0變為1了。

 1[zk: localhost:2181(CONNECTED) 18] stat /Java
2cZxid = 0x14
3ctime = Mon May 11 21:40:38 CST 2020
4mZxid = 0x16
5mtime = Mon May 11 21:52:19 CST 2020
6pZxid = 0x15
7cversion = 1
8dataVersion = 1
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 1

因為剛才更新資料內容的操作導致資料版本升級。

刪除

使用delete命令刪除Zookeeper節點,用法如下:

1 delete [-v version] path

比如我們將/Java節點刪除掉。不過需要注意的是,要刪除的節點必須沒有子節點才可以。下面直接刪除/Java節點是刪除不掉的。因為它下面有子節點。

1[zk: localhost:2181(CONNECTED) 20] delete /Java
2Node not empty: /Java
3[zk: localhost:2181(CONNECTED) 21] ls /
4[Java, aaa, happy, test, zookeeper]

可以刪除/Java/spring節點

1[zk: localhost:2181(CONNECTED) 22] ls /Java
2[spring]
3[zk: localhost:2181(CONNECTED) 23] delete /Java/spring
4[zk: localhost:2181(CONNECTED) 24] ls /Java
5[]

ZAB協議

ZAB協議是為分散式協調服務Zookeeper專門設計的一種支援崩潰恢復的原子廣播協議。它並不是Paxos演算法的一種實現。

在Zookeeper中,主要依賴ZAB協議來實現分散式資料一致性,基於該協議,Zookeeper實現了一種主備模式的系統架構來保證叢集模中各副本之間資料的一致性。ZAB協議要滿足下面一些核心需求。

  • Zookeeper使用一個單一的主程式來接收和處理客戶端的所有事務請求,並採用ZAB的原子廣播協議,將伺服器資料狀態的變更以事務Proposal的形式廣播到所有副本程式上去。
  • 要保證事務執行的順序性。ZAB協議必須保證一個全域性的變更系列被順序地應用。
  • 最後就是考慮到主程式(也就是Leader伺服器)隨時都有可能崩潰或者退出。ZAB協議要做到Leader在出現上述異常的情況下,依然能夠正常的工作。

ZAB協議的核心機制

其核心機制是定義了對於那些會改變Zookeeper伺服器資料狀態的事務請求的處理方式,即:

所有的事務請求必須由一個全域性唯一的伺服器來協調處理,這樣的伺服器稱為Leader伺服器,而餘下的其他伺服器是Follower(這裡暫不說Observer,因為不參與投票)。Leader伺服器複雜將一個客戶端的事務請求轉換成一個事務提議(Proposal),並將該Proposal分發給叢集中所有的Follower伺服器。之後Leader伺服器需要等待所有Follower伺服器的反饋,一旦超過半數的Follower伺服器進行了正確地反饋後,那麼Leader就會再次向所有的Follower伺服器分發Commit訊息,要求其將前一個Proposal進行提交。

針對ZAB協議這裡只做簡要介紹,至於崩潰恢復和訊息廣播的具體內容不詳細展開。

Zookeeper的Leader選舉

前面說到在ZK叢集中,有一個Leader負責處理事務請求。Leader是通過一種選舉演算法選出來的一個Zookeeper伺服器節點。下面簡要介紹下Leader選舉的過程。

Leader選舉有兩個時機:

1、伺服器啟動時Leader選舉
2、執行期Leader節點掛了,需要選舉新的Leader。

伺服器啟動時的Leader選舉

這裡以3臺機器組成的叢集為例子。Server1(myid為1)、Server2(myid為2)、Server3(myid為3)。myid是在zk叢集中用來標識每一臺機器的,不能重複。假設Server1最先啟動,然後Server2,再Server3。

1、首先每臺Server會發出一個投票

由於是初始的情況,每臺機器都會選自己作為Leader。每次投票的最基本資訊就是伺服器的myid和ZXID,我們用(myid,zxid)這種形式標識。那Server1發出的投票就是(1,0),Server2發出的投票就是(2,0),Server3(3,0)然後將各自的投票傳送給叢集中剩下的其他所有機器。

2、接收來自各個伺服器的投票

每個伺服器都會接收來自其他伺服器的投票。叢集中每個伺服器在接收到投票後,首先會判斷該投票的有效性,包括檢查投票是否是本輪投票,是否來自LOOKING狀態的伺服器。

3、處理投票

主要是拿自己的投票和其他伺服器傳送過來的投票做一個PK,PK的規則如下:

  • 有限檢查ZXID。ZXID比較大的伺服器優先作為Leader
  • 如果ZXID相同的話,那麼就比較myid。myid比較大的伺服器作為Leader。

這裡對於Server1來說,自己的投票是(1,0),收到的投票是(2,0),經過PK之後,Server1會更新自己的投票(2,0),然後將票重新發出去。而對於Server2來說,不需要更新自己的投票資訊。

4、統計投票資訊

每次投票後,伺服器都會統計所有的投票,判斷是否已經有過半的機器收到了相同的投票資訊。這裡對於Server1,Server2來說,都統計出叢集中已經有兩臺機器接收了(2,0)這個投票資訊。此時,就認為已經選舉出了Leader

5、改變伺服器狀態

一旦確定了Leader,每個伺服器都會更新自己的狀態:如果是Follower,那麼就變為FOLLOWING,如果是Leader,那麼就變為LEADING。

執行期Leader節點掛了,需要選舉新的Leader。

zk叢集正常執行的過程中,一旦選出了Leader,那它一直就是Leader,除非這個Leader掛了,才會進入新一輪的Leader選舉,也就是下面要說的這種情況。這個過程其實和啟動期間Leader選擇過程基本是一致的。

1、變更伺服器狀態

當Leader掛了之後,餘下的非Observer伺服器都會將自己的伺服器狀態變為LOOKING,然後開始進入Leader選舉流程。

2、每個Server會發出一個投票

這裡ZXID可能就會是不一樣的,因為是執行期,每臺機器上的資料同步情況可能會有差異。

3、接收來自各個伺服器的投票

4、處理投票

5、統計投票

6、改變伺服器的狀態

Zookeeper的典型應用場景

1、使用Zookeeper可以做配置中心

可以將配置資訊集中儲存在zk的節點中,客戶端可以註冊一些監聽,一旦節點資料發生變更,服務端就會向相應的客戶端傳送Watcher事件通知,客戶端接收到這個通知之後,可以主動到服務端獲取最新的資料。

2、作為註冊中心

Dubbo中就是預設用zk作為註冊中心的。將服務的url資訊註冊到zk的節點上。利用臨時節點和watcher機制實現服務的動態感知。

3、作為分散式鎖

分散式鎖是分散式場景下保證資源同步的一種方式。在zk中,所有客戶端都在一個節點(比如/lock)下呼叫create()方法建立臨時子節點,只會有一個建立成功,這個建立成功的就認為是獲取了鎖。其他客戶端就需要在/lock節點上註冊一個子節點變更的watcher監聽。

相關文章