Zookeeper系列(4)--ZK概述,數據模型,節點特性,Watcher機制、ACL及數據存儲

在zookeeper系列的前三篇,介紹分佈式數據一致性的相關原理及經典的分佈式一致性算法,比如:2PC,3PC,Paxos算法。在本篇,我們正式開始介紹Zookeeper,Zookeeper是分佈式一致性問題的工業解決方案,是常用的分佈式協調框架。本篇,會介紹Zookeeper的基本概念,數據模型,節點特性,Watcher機制及ACL等機制,在後邊我們會介紹Zookeeper爲了保證一致性使用的算法ZAB,以及Zookeeper的應用場景。

Zookeeper基本概述

Zookeeper爲分佈式應用提供了高效且可靠的分佈式協調服務,其實現依賴於ZAB協議,實現了一種主備模式的架構來保持數據的一致性(Zookeeper本身可保證分佈式數據的一致性,從而可以提供高效可靠的協調服務)。
Zookeeper致力於提供一個高性能、高可用,且具有嚴格的順序訪問控制能力(主要是寫操作的嚴格順序性)的分佈式協調服務。可用於大型的分佈式系統中。Zookeeper作爲分佈式協調服務提供了諸如統一命名服務,配置管理和分佈式鎖等分佈式的基礎服務。在解決分佈式數據的一致性方面,Zookeeper並沒有直接採用Paxos算法,而是採用了一種被稱爲ZAB(Zookeeper Atomic Broadcast)原子廣播協議的一致性協議。ZK是Google的Chubby(基於paxos算法的分佈式鎖服務)一個開源的實現。由於ZK自身的高可用性和可保持一致性,所以可以將其用於很多系統服務:分負載均衡,命名服務,分佈式鎖,集羣管理,master選舉等,都是zk提高的協調服務。

Zookeeper可以保證如下的分佈式一致性要求:

順序一致性
從同一個客戶端發起的事務請求,最終將會嚴格按照其發起順序被應用到ZooKeeper中。
原子性
所有事務請求的結果在集羣中所有機器上的應用情況是一致的,也就是說要麼整個集羣所有集羣都成功應用了某一個事務,要麼都沒有應用,一定不會出現集羣中部分機器應用了該事務,而另外一部分沒有應用的情況。
單一視圖
無論客戶端連接的是哪個ZooKeeper服務器,其看到的服務端數據模型都是一致的。
可靠性
一旦服務端成功地應用了一個事務,並完成對客戶端的響應,那麼該事務所引起的服務端狀態變更將會被一直保留下來,除非有另一個事務又對其進行了變更。
實時性
通常人們看到實時性的第一反應是,一旦一個事務被成功應用,那麼客戶端能夠立即從服務端上讀取到這個事務變更後的最新數據狀態。這裏需要注意的是,ZooKeeper僅僅保證一定的時間段內,客戶端最終一定能夠從服務端上讀取到最新的數據狀態。

注意:

在上邊的特性中,順序訪問,對於來自客戶端的每個請求,Zookeeper都會分配一個全局唯一的遞增編號,編號反映了所有事務操作的先後順序,並且在進行廣播時,也是按照順序執行的,因爲TCP的連接,對於一個服務器向另一個服務器發送的信息,肯定是接收順序肯定是和發送順序一致的。??

集羣角色

在ZooKeeper中,有三種角色:

  • Leader
  • Follower
  • Observer

一個ZooKeeper集羣同一時刻只會有一個Leader,其他都是Follower或Observer。2181端口。

Zookeeper集羣中的任何一臺機器都可以響應客戶端的讀操作,且全量數據都存在於內存中,因此Zookeeper更適合以讀操作爲主的應用場景。注意,當不是leader的服務器收到客戶端事務操作,他會將其轉發到Leader,讓Leader進行處理。

ZooKeeper集羣的所有機器通過一個Leader選舉過程來選定一臺被稱爲『Leader』的機器,Leader服務器爲客戶端提供服務。Follower和Observer都提供服務,不能提供服務。兩者唯一的區別在於,Observer機器不參與Leader選舉過程,也不參與寫操作的『過半寫成功』策略,因此Observer可以在不影響寫性能的情況下提升集羣的讀性能
Zookeeper集羣的數量爲奇數個(注意:這裏並不是說偶數個就不行,而是:比如:5臺和6臺集羣的容災能力是一樣的,所以我們可以少用一臺達到相同的目的)。

基本架構:

1 每個Server在內存中存儲了一份數據; 
2 Zookeeper啓動時,將從實例中選舉一個leader(Paxos協議); 
3 Leader負責處理數據更新等操作(Zab協議); 
4 一個更新操作成功,當且僅當大多數Server在內存中成功修改 

會話

Session是指客戶端會話,在講解客戶端會話之前,我們先來了解下客戶端連接。在ZooKeeper中,一個客戶端連接是指客戶端和ZooKeeper服務器之間的TCP長連接。ZooKeeper對外的服務端口默認是2181,客戶端啓動時,首先會與服務器建立一個TCP連接,從第一次連接建立開始,客戶端會話的生命週期也開始了,通過這個連接,客戶端能夠通過心跳檢測和服務器保持有效的會話,也能夠向ZooKeeper服務器發送請求並接受響應,同時還能通過該連接接收來自服務器的Watch事件通知。Session的SessionTimeout值用來設置一個客戶端會話的超時時間。當由於服務器壓力太大、網絡故障或是客戶端主動斷開連接等各種原因導致客戶端連接斷開時,只要在SessionTimeout規定的時間內能夠重新連接上集羣中任意一臺服務器,那麼之前創建的會話仍然有效。

Zookeeper數據模型

zookeeper採用層次化的目錄結構,命名符合常規文件系統規範; 每個目錄在zookeeper中叫做znode,並且其有一個唯一的路徑標識; Znode可以包含數據和子znode(ephemeral(臨時)類型的節點不能有子znode); Znode中的數據可以有多個版本,比如某一個znode下存有多個數據版本,那麼查詢這個路徑下的數據需帶上版本; 客戶端應用可以在znode上設置監視器(Watcher) 。
ZNode可以保存數據,同時還可以掛載子節點,因此構成了一個層次化的樹形命名空間。Znode的節點路徑標識方式和Unix文件系統路徑非常相似,用一系列(\)進行分割。

節點特性

 ZooKeeper 節點是有生命週期的,這取決於節點的類型。在 ZooKeeper 中,節點類型可以分爲持久節點(PERSISTENT 臨時節點(EPHEMERAL,以及順序節點(SEQUENTIAL ,具體在節點創建過程中,一般是組合使用,可以生成以下 4 種節點類型。

持久節點(PERSISTENT

        所謂持久節點,是指在節點創建後,就一直存在,直到有刪除操作來主動清除這個節點——不會因爲創建該節點的客戶端會話失效而消失。

 持久順序節點(PERSISTENT_SEQUENTIAL

       ZK中,每個父節點會爲他的第一級子節點維護一份時序,會記錄每個子節點創建的先後順序。基於這個特性,在創建子節點的時候(注意:在此節點下的子節點是由順序的),可以設置這個屬性,那麼在創建節點過程中,ZK會自動爲給定節點名加上一個數字後綴,作爲新的節點名。這個數字後綴的上限是整型的最大值。

臨時節點(EPHEMERAL

      和持久節點不同的是,臨時節點的生命週期和客戶端會話綁定。也就是說,如果客戶端會話失效,那麼這個節點就會自動被清除掉。注意,這裏提到的是會話失效,而非連接斷開。另外,在臨時節點下面不能創建子節點,注意是更具Session會話的失效時間來設定的。

臨時順序節點(EPHEMERAL_SEQUENTIAL

     臨時順序節點的特性和臨時節點一致,同時是在臨時節點的基礎上,添加了順序的特性。


 Zookeeper維護數據節點的同時,每個節點除了存儲數據內容之外,還存儲了數據節點本身一些狀態信息,可通過get來獲得。

    ZooKeeper中每個znodeStat結構體由下述字段構成:

  •     czxid:創建節點的事務的zxid
  •     mzxid:對znode最近修改的zxid
  •     ctime:以距離時間原點(epoch)的毫秒數表示的znode創建時間
  •     mtime:以距離時間原點(epoch)的毫秒數表示的znode最近修改時間
  •     versionznode數據的修改次數
  •     cversionznode子節點修改次數
  •     aversionznodeACL修改次數
  •     ephemeralOwner:如果znode是臨時節點,則指示節點所有者的會話ID;如果不是臨時節點,則爲零。
  •     dataLengthznode數據長度。
  •     numChildrenznode子節點個數。

版本--保證分佈式數據原子操作

Zookeeper會爲每個Znode維護一個叫作Stat的數據結構,結構如圖:存在三個版本信息:
  •     versionznode數據的修改次數
  •     cversionznode子節點修改次數
  •     aversionznodeACL修改次數
   version是表示對數據節點數據內容的變更次數,強調的是變更次數,因此就算數據內容的值沒有發生變化,version的值也會遞增。

 在介紹version時,我們可以簡單的瞭解在數據庫技術中,通常提到的悲觀鎖樂觀鎖

    悲觀鎖:具有嚴格的獨佔和排他特性,能偶有效的避免不同事務在同一數據併發更新而造成的數據一致性問題。實現原理就是:假設A事務正在對數據進行處理,那麼在整個處理過程中,都會將數據處於鎖定的狀態,在這期間,其他事務將無法對這個數據進行更新操作,直到事務A完成對該數據的處理,釋放對應的鎖。一份數據只會分配一把鑰匙,如數據庫的表鎖或者行鎖(for update).

    樂觀鎖:具體實現是,表中有一個版本字段,第一次讀的時候,獲取到這個字段。處理完業務邏輯開始更新的時候,需要再次查看該字段的值是否和第一次的一樣。如果一樣更新,反之拒絕。樂觀鎖就是假定多個事務在處理過程中不會影響彼此,悲觀鎖正好相反,因此樂觀鎖在事務處理的絕大部分時間裏不需要進行加鎖處理。樂觀鎖非常適用於在數據競爭不大,事務衝突較少的應用場景中。最經典的應用就是:JDK中的CAS處理。

     Zookeeper的版本作用就是類似於樂觀鎖機制,用於實現樂觀鎖機制的寫入校驗”.


從上邊的邏輯,我們可以看到,當對數據節點狀態進行改變時,首先要進行數據節點版本檢查,如果首先獲得當前請求的版本,然後再從數據積累獲取當前服務器上該數據的最新版本,如果version=-1,則客戶端不要求使用樂觀鎖,可以忽略此版本檢查,如果不是-1,就進行檢查,匹配才進行數據變更,否則拋出異常。


Watcher機制

Watcher機制:目的是爲ZK客戶端操作提供一種類似於異步獲得數據的操作。zk提供了分佈式數據的發佈訂閱功能,一個典型的發佈訂閱系統定義了一種一對多的訂閱關係,能夠讓多個訂閱者同時監聽某一個主題對象,當這個主題對象自身狀態變化時,會通知所有訂閱者,使它們能夠做出相應的處理。zk,引入了watcher機制來實現這種分佈式的通知功能。

zk允許客戶端向服務端註冊一個watcher監聽,當服務端的一些指定事件觸發這個watcher,那麼就會向指定客戶端發送一個事件通知。

  ZookeeperWatcher機制主要包括客戶端線程、客戶端WatchManagerZookeeper服務器三部分。在具體的流程上,客戶端向Zookeeper服務器註冊Watcher事件監聽的同時,會將Watcher對象存儲在 客戶端WatchManager中。當Zookeeper服務器觸發Watcher事件後,會向客戶端發送通知,客戶端線程從WatchManager中取出對應的Watcher對象執行回調邏輯。

watcher機制特點:

 1.一次性觸發  數據發生改變時,一個watcher event會被髮送到client,但是client只會收到一次這樣的信息。

   2.watcher event異步發送   watcher 的通知事件從server發送到client是異步的,這就存在一個問題,不同的客戶端和服務器之間通過socket進行通信,由於網絡延遲或其他因素導致客戶端在不通的時刻監聽到事件,由於Zookeeper本身提供了ordering guarantee,即客戶端監聽事件後,纔會感知它所監視znode發生了變化。

   3.數據監視   Zookeeper有數據監視和子數據監視   getdata() and exists() 設置數據監視,getchildren()設置了子節點監視

其監聽的事件有:


     NodeDataChanged事件:此處的變更包括數據節點內容和數據的版本號DateVersion。因此,對於Zookeeper來說,無論數據內容是否更改,還是會觸發這個事件的通知,一旦客戶端調用了數據更新接口,且更新成功,就會更新dataversion值。


關於watcher機制的實現,建議看《從paxos到zookeeper分佈式一致性原理與實踐》。有點複雜。

ACL--保障數據的安全

ACL全稱爲Access Control List(訪問控制列表),用於控制資源的訪問權限。zk利用ACL策略控制節點的訪問權限,如節點數據讀寫、節點創建、節點刪除、讀取子節點列表、設置節點權限等。
在傳統的文件系統中,ACL分爲兩個維度,一個是屬組,一個是權限,一個屬組包含多個權限,一個文件或目錄擁有某個組的權限即擁有了組裏的所有權限,文件或子目錄默認會繼承自父目錄的ACL。(如linux系統的權限控制)
而在Zookeeper中,znode的ACL是沒有繼承關係的,每個znode的權限都是獨立控制的,只有客戶端滿足znode設置的權限要求時,才能完成相應的操作。Zookeeper的ACL,分爲三個維度:scheme、id、permission,通常表示爲:scheme:id:permissionschema代表授權策略,id代表用戶,permission代表權限。下面從這三個維度分別來介紹。

id是驗證模式,不同的scheme,id的值也不一樣。scheme爲digest時,id的值爲:username:BASE64(SHA1(password)),scheme爲ip時,id的值爲客戶端的ip地址。scheme爲world時,id的值爲anyone

scheme

scheme即採取的授權策略,每種授權策略對應不同的權限校驗方式。下面是zk常用的幾種scheme:

1. digest

語法:digest:username:BASE64(SHA1(password)):cdrwa 
digest:是授權方式 
username:BASE64(SHA1(password)):是id部分 
cdrwa:權限部份 

用戶名+密碼授權訪問方式,也是常用的一種授權策略。id部份是用戶名和密碼做sha1加密再做BASE64加密後的組合。

使用:

## 創建節點/node_05
shell> create /node_05 data
Created /node_05
## 設置權限
shell> setAcl /node_05 digest:yangxin:ACFm5rWnnKn9K9RN/Oc8qEYGYDs=:cdrwa
cZxid = 0x8e
ctime = Mon Nov 14 21:38:52 CST 2016
mZxid = 0x8e
mtime = Mon Nov 14 21:38:52 CST 2016
pZxid = 0x8e
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
## 獲取節點剛剛設置的權限
shell> getAcl /node_05
'digest,'yangxin:ACFm5rWnnKn9K9RN/Oc8qEYGYDs=
: cdrwa

## 沒有授權,創建節點失敗
shell> create /node_05/node_05_01 data
Authentication is not valid : /node_05/node_05_01

## 添加授權信息
shell> addauth digest yangxin:123456

## 添加授權信息後,就可以正常操作了
shell> create /node_05/node_05_01 data
Created /node_05/node_05_01
2. IP

基於客戶端IP地址校驗,限制只允許指定的客戶端能操作znode。 
比如,設置某個節點只允許IP爲192.168.1.100的客戶端能讀寫該寫節點的數據:ip:192.168.1.100:rw

3. world
語法:world:anyone:cdrwa 
創建節點默認的scheme,所有人都可以訪問。如下所示:

上面主要介紹了平時常用的三種scheme,除此之外,還有host、super(管理員超級用戶)、auth授權策略。

permission


在介紹scheme的時候,提到了acl的權限,如:digest:username:BASE64(SHA1(password)):cdrwa中的cdrwa即是permission。 
1> CREATE(r):創建子節點的權限 
2> DELETE(d):刪除節點的權限 
3> READ(r):讀取節點數據的權限 
4> WRITE(w):修改節點數據的權限 
5> ADMIN(a):設置子節點權限的權限

注意:cd權限用於控制子節點,rwa權限用於控制節點本身

Zookeeper內存模型

 Zookeeper的數據模型是樹結構,在內存數據庫中,存儲了整棵樹的內容,包括所有的節點路徑、節點數據、ACL信息,Zookeeper會定時將這個數據存儲到磁盤上。

  1. DataTree

  DataTree是內存數據存儲的核心,是一個樹結構,代表了內存中一份完整的數據。DataTree不包含任何與網絡、客戶端連接及請求處理相關的業務邏輯,是一個獨立的組件。

  2. DataNode

  DataNode是數據存儲的最小單元,其內部除了保存了結點的數據內容、ACL列表、節點狀態之外,還記錄了父節點的引用和子節點列表兩個屬性,其也提供了對子節點列表進行操作的接口。

  3. ZKDatabase

  Zookeeper的內存數據庫,管理Zookeeper的所有會話、DataTree存儲和事務日誌。ZKDatabase會定時向磁盤dump快照數據,同時在Zookeeper啓動時,會通過磁盤的事務日誌和快照文件恢復成一個完整的內存數據庫。


DataTree是整個樹的核心,不與任何網絡、客戶端以及請求事務有關。DataTree利用CurrentHashMap<String,DataNode>的屬性nodes來存儲着整個樹的所有節點以及對應的路徑,對於臨時節點單獨放在一個CurrentHashMap中。DataNode是最小的存儲單元,保存着節點的數據,ACL,父節點和子節點列表。

對於目標節點的查找並不是使用樹的結構層層查找,而是在DataTree中的屬性nodes--CurrentHashMap<String,DataNode>根據路徑作爲key直接查找DataNode,提高了查找效率。

Zookeeper數據與存儲

我們知道Zookeeper是將全量數據存儲在內存中,但他是怎樣進行容錯的呢?當節點崩潰後或重新初始化時,是怎麼會發到宕機之前的數據呢?這就需要Zookeeper的數據存儲實現。感覺大多數內存存儲的組件容錯機制都差不多,都是利用快照和事務日誌來保證節點宕機恢復工作。比如:zookeeper,HDFS的namenode,redis,都是採用快照+事務日誌來進行數據持久化,來實現底層數據的一致性。

事務日誌:
在配置Zookeeper集羣時需要配置dataDir目錄,其用來存儲事務日誌文件。也可以爲事務日誌單獨分配一個文件存儲目錄:dataLogDir。若配置dataLogDir爲/home/admin/zkData/zk_log,那麼Zookeeper在運行過程中會在該目錄下建立一個名字爲version-2的子目錄,該目錄確定了當前Zookeeper使用的事務日誌格式版本號,當下次某個Zookeeper版本對事務日誌格式進行變更時,此目錄也會變更,即在version-2子目錄下會生成一系列文件大小一致(64MB)的文件。

事務日誌記錄了對Zookeeper的操作,命名爲log.ZXID,後綴是一個事務ID。並且是寫入該事務日誌文件第一條事務記錄的ZXID,使用ZXID作爲文件後綴,可以幫助我們迅速定位到某一個事務操作所在的事務日誌。同時,使用ZXID作爲事務日誌後綴的另一個優勢是:ZXID本身由兩部分組成,高32位代表當前leader週期(epoch),低32位則是真正的操作序列號,因此,將ZXID作爲文件後綴,我們就可以清楚地看出當前運行時的zookeeper的leader週期。

事務日誌的寫入是採用了磁盤預分配的策略。因爲事務日誌的寫入性能直接決定看Zookeeper服務器對事務請求的響應,也就是說事務寫入可被看做是一個磁盤IO過程,所以爲了提高性能,避免磁盤尋址seek所帶來的性能下降,所以zk在創建事務日誌的時候就會進行文件空間“預分配”,即:在文件創建之初就想操作系統預分配一個很大的磁盤塊,默認是64M,而一旦已分配的文件空間不足4KB時,那麼將會再次進行預分配,再申請64M空間。

數據快照:

 數據快照是Zookeeper數據存儲中非常核心的運行機制,數據快照用來記錄Zookeeper服務器上某一時刻的全量內存數據內容,並將其寫入指定的磁盤文件中。也是使用ZXID來作爲文件 後綴名,並沒有採用磁盤預分配的策略,因此數據快照文件在一定程度上反映了當前zookeeper的全量數據大小。
  與事務文件類似,Zookeeper快照文件也可以指定特定磁盤目錄,通過dataDir屬性來配置。若指定dataDir爲/home/admin/zkData/zk_data,則在運行過程中會在該目錄下創建version-2的目錄,該目錄確定了當前Zookeeper使用的快照數據格式版本號。在Zookeeper運行時,會生成一系列文件。
針對客戶端的每一次事務操作,Zookeeper都會將他們記錄到事務日誌中,同時也會將數據變更應用到內存數據庫中,Zookeeper在進行若干次(snapCount)事務日誌記錄後,將內存數據庫的全量數據Dump到本地文件中,這就是數據快照。

過半隨機策略:每進行一次事務記錄後,Zookeeper都會檢測當前是否需要進行數據快照。理論上進行snapCount次事務操作就會開始數據快照,但是考慮到數據快照對於Zookeeper所在機器的整體性能的影響,需要避免Zookeeper集羣中所有機器在同一時刻進行數據快照。因此zk採用“過半隨機”的策略,來判斷是否需要進行數據快照。即:符合如下條件就可進行數據快照:

logCount > (snapCount / 2 + randRoll)   randRoll位1~snapCount / 2之間的隨機數。這種策略避免了zk集羣的所有機器在同一時刻都進行數據快照,影響整體性能。

進行快照:
開始快照時,首先關閉當前日誌文件(已經到了該快照的數了),重新創建一個新的日誌文件,創建單獨的異步線程來進行數據快照以避免影響Zookeeper主流程,從內存中獲取zookeeper的全量數據和校驗信息,並序列化寫入到本地磁盤文件中,以本次寫入的第一個事務ZXID作爲後綴。

數據恢復:

在Zookeeper服務器啓動期間,首先會進行數據初始化工作,用於將存儲在磁盤上的數據文件加載到Zookeeper服務器內存中。

數據恢復時,會加載最近100個快照文件(如果沒有100個,就加載全部的快照文件)。之所以要加載100個,是因爲防止最近的那個快照文件不能通過校驗。在逐個解析過程中,如果正確性校驗通過之後,那麼通常就只會解析最新的那個快照文件,但是如果校驗和發現最先的那個快照文件不可用,那麼就會逐個進行解析,直到將這100個文件全部解析完。如果將所有的快照文件都解析後還是無法恢復出一個完整的“DataTree”和“sessionWithTimeouts”,則認爲無法從磁盤中加載數據,服務器啓動失敗。當基於快照文件構建了一個完整的DataTree實例和sessionWithTimeouts集合了,此時根據這個快照文件的文件名就可以解析出最新的ZXID,該ZXID代表了zookeeper開始進行數據快照的時刻,然後利用此ZXID定位到具體事務文件從哪一個開始,然後執行事務日誌對應的事務,恢復到最新的狀態,並得到最新的ZXID。


在本篇中,我們詳細介紹了zookeeper的概念和相關用法,在zookeeper的應用中,我們會發現zookeeper提供的服務基本都是基於節點類型和watcher機制實現的,很多都可以實現這個功能啊,爲什麼zk就是經典的分佈式服務框架呢,主要是zk是高性能,高可用,以及能保證數據一致性的。那麼它是如何保證高可用以及數據一致性呢,這就涉及到其底層一致性協議,zab協議!在下一篇將進行解釋。





















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章