問題導讀:
1.ZooKeeper的數據模型是什么 ?
2.ZooKeeper應用有哪些陷阱 ?
3.每個節點(ZNode)中存儲的是什么?
4.一個ZNode維護了一個狀態結構都包含了什么?
5.ZNode組成結構是什么?
6.Watches的機制是什么?
7.ZooKeeper內置了哪4種方式實現ACL?
前言
ZooKeeper
是一個開源的分布式服務框架,它是ApacheHadoop
項目的一個子項目,主要用來解決分布式應用場景中存在的一些問題,如:統一命名服務、狀態同步服務、集群管理、分布式應用配置管理等,它支持Standalone
模式和分布式模式,在分布式模式下,能夠為分布式應用提供高性能和可靠地協調服務,而且使用ZooKeeper
可以大大簡化分布式協調服務的實現,為開發分布式應用極大地降低了成本。
總體架構
ZooKeeper
分布式協調服務框架的總體架構,如圖所示:
<ignore_js_op>

ZooKeeper
集群由一組Server
節點組成,這一組Server
節點中存在一個角色為Leader
的節點,其他節點都為Follower
。當客戶端Client
連接到ZooKeeper
集群,並且執行寫請求時,這些請求會被發送到Leader
節點上,然后Leader
節點上數據變更會同步到集群中其他的Follower
節點。
Leader
節點在接收到數據變更請求后,首先將變更寫入本地磁盤,以作恢復之用。當所有的寫請求持久化到磁盤以后,才會將變更應用到內存中。
ZooKeeper
使用了一種自定義的原子消息協議,在消息層的這種原子特性,保證了整個協調系統中的節點數據或狀態的一致性。Follower
基於這種消息協議能夠保證本地的ZooKeeper
數據與Leader
節點同步,然后基於本地的存儲來獨立地對外提供服務。
當一個Leader
節點發生故障失效時,失敗故障是快速響應的,消息層負責重新選擇一個Leader
,繼續作為協調服務集群的中心,處理客戶端寫請求,並將ZooKeeper
協調系統的數據變更同步(廣播)到其他的Follower
節點。
設計要點
ZooKeeper
是基於如下4
個目標來進行權衡和設計的,我們從設計及其特性的角度來詳細說明:
簡單
分布式應用中的各個進程可以通過ZooKeeper
的命名空間(Namespace
)來進行協調,這個命名空間是共享的、具有層次結構的,更重要的是它的結構足夠簡單,像我們平時接觸到的文件系統的目錄結構一樣容易理解,如圖所示:
<ignore_js_op>

在ZooKeeper
中每個命名空間(Namespace
)被稱為ZNode
,你可以這樣理解,每個ZNode
包含一個路徑和與之相關的元數據,以及繼承自該節點的孩子列表。與傳統文件系統不同的是,ZooKeeper
中的數據保存在內存中,實現了分布式同步服務的高吞吐和低延遲。
在上圖示例的ZooKeeper
的數據模型中,有如下要點:
每個節點(ZNode
)中存儲的是同步相關的數據(這是ZooKeeper
設計的初衷,數據量很小,大概B
到KB
量級),例如狀態信息、配置內容、位置信息等。
一個ZNode
維護了一個狀態結構,該結構包括:版本號、ACL
變更、時間戳。每次ZNode
數據發生變化,版本號都會遞增,這樣客戶端的讀請求可以基於版本號來檢索狀態相關數據。
每個ZNode
都有一個ACL
,用來限制是否可以訪問該ZNode
。
在一個命名空間中,對ZNode
上存儲的數據執行讀和寫請求操作都是原子的。
ZooKeeper
支持臨時節點(EphemeralNodes
)的概念,它是與ZooKeeper
中的會話(Session
)相關的,如果連接斷開,則該節點被刪除。
冗余
ZooKeeper
被設計為復制集群架構,每個節點的數據都可以在集群中復制傳播,使集群中的每個節點數據同步一致,從而達到服務的可靠性和可用性。前面說到,ZooKeeper
將數據放在內存中來提高性能,為了避免發生單點故障(SPOF
),支持數據的復制來達到冗余存儲,這是必不可少的。
有序
ZooKeeper
使用時間戳來記錄導致狀態變更的事務性操作,也就是說,一組事務通過時間戳來保證有序性。基於這一特性。ZooKeeper
可以實現更加高級的抽象操作,如同步等。
快速
ZooKeeper
包括讀寫兩種操作,基於ZooKeeper
的分布式應用,如果是讀多寫少的應用場景(讀寫比例大約是10:1
),那么讀性能更能夠體現出高效。
數據模型
ZooKeeper
有一個分層的命名空間,結構類似文件系統的目錄結構,非常簡單而直觀。其中,ZNode
是最重要的概念,前面我們已經描述過。另外,有ZNode
有關的還包括Watches
、ACL
、臨時節點、序列節點(Sequence Node
)。
ZNode
結構
ZooKeeper
中使用Zxid
(ZooKeeperTransaction Id
)來表示每次節點數據變更,一個Zxid
與一個時間戳對應,所以多個不同的變更對應的事務是有序的。下面是ZNode
的組成結構,引用文檔如下所示:
czxid – The zxid of the change that causedthis znode to be created.
mzxid – The zxid of the change that lastmodified this znode.
ctime – The time in milliseconds from epochwhen this znode was created.
mtime – The time in milliseconds from epochwhen this znode was last modified.
version – The number of changes to the dataof this znode.
cversion – The number of changes to thechildren of this znode.
aversion – The number of changes to the ACLof this znode.
ephemeralOwner – The session id of theowner of this znode if the znode is an ephemeral node. If it is not anephemeral node, it will be zero.
dataLength – The length of the data fieldof this znode.
numChildren – The number of children ofthis znode.
Watches
(監視)
ZooKeeper
中的Watch
是只能觸發一次。也就是說,如果客戶端在指定的ZNode
設置了Watch
,如果該ZNode
數據發生變更,ZooKeeper
會發送一個變更通知給客戶端,同時觸發設置的Watch
事件。如果ZNode
數據又發生了變更,客戶端在收到第一次通知后沒有重新設置該ZNode
的Watch
,則ZooKeeper
就不會發送一個變更通知給客戶端。
ZooKeeper
異步通知設置Watch
的客戶端。但是ZooKeeper
能夠保證在ZNode
的變更生效之后才會異步地通知客戶端,然后客戶端才能夠看到ZNode
的數據變更。由於網絡延遲,多個客戶端可能會在不同的時間看到ZNode
數據的變更,但是看到變更的順序是能夠保證有序一致的。
ZNode
可以設置兩類Watch
,一個是DataWatches
(該ZNode
的數據變更導致觸發Watch
事件),另一個是Child Watches
(該ZNode
的孩子節點發生變更導致觸發Watch
事件)。調用getData()
和exists()
方法可以設置Data Watches
,調用getChildren()
方法可以設置Child Watches
。調用setData()
方法觸發在該ZNode
的注冊的Data Watches
。調用create()
方法創建一個ZNode
,將觸發該ZNode
的Data Watches
;調用create()
方法創建ZNode
的孩子節點,則觸發ZNode
的Child Watches
。調用delete()
方法刪除ZNode
,則同時觸發Data Watches
和Child Watches
,如果該被刪除的ZNode
還有父節點,則父節點觸發一個Child Watches
。
Sequence Nodes
(序列節點)
在創建ZNode
的時候,可以請求ZooKeeper
生成序列,以路徑名為前綴,計數器緊接在路徑名后面,例如,會生成類似如下形式序列
qn-0000000001, qn-0000000002,qn-0000000003, qn-0000000004, qn-0000000005, qn-0000000006, qn-0000000007
ACLs
(訪問控制列表)
ACL
可以控制訪問ZooKeeper
的節點,只能應用於特定的ZNode
上,而不能應用於該ZNode
的所有孩子節點上。它主要有如下五種權限:
CREATE
允許創建Child Nodes
READ
允許獲取ZNode
的數據,以及該節點的孩子列表
WRITE
可以修改ZNode
的數據
DELETE
可以刪除一個孩子節點
ADMIN
可以設置權限
ZooKeeper
內置了4
種方式實現ACL
:
world
一個單獨的ID
,表示任何人都可以訪問
auth
不使用ID
,只有認證的用戶可以訪問
digest
使用username:password
生成MD5
哈希值作為認證ID
ip
使用客戶端主機IP
地址來進行認證
ZooKeeper Session
當客戶端連接到ZooKeeper
集群時,建立了會話。會話過程中的狀態變遷,如圖所示:
<ignore_js_op>

建立連接過程中,會話狀態為CONNECTING
;當連接建立成功后,會話狀態變為CONNECTED
。會話過程中,如果正常的話,會話的狀態只能是CONNECTING
和CONNECTED
二者之一。如果在會話過程中連接斷開,則變為CLOSED
狀態。
應用陷阱
並非任何分布式應用都適合使用ZooKeeper
來構建協調服務,我們根據ZooKeeper
提供的文檔,給出哪些情況下使用會出現問題,又是如何應對這種問題的。
總結如下:
丟失ZNode
上的變更通知
客戶端連接到ZooKeeper Server
以后,會維護一個TCP
連接。在CONNECTED
狀態下,客戶端設置了某個ZNode
的Watch
監聽器,可以收到來自該節點變更的通知(后續會觸發一定的邏輯執行流程)。但是,如果由於網絡異常,客戶端斷開了與ZooKeeper Server
的連接,在斷開的過程中,是無法收到ZooKeeper
在ZNode
上發送的節點數據變更通知的。
所以,如果使用ZooKeeper
的Watch
,必須要尋找保持CONNECTED
的Watch
,才能保證不會丟失該Watch
監控的ZNode
上的數據變更通知。
無效ZooKeeper
集群節點列表
一種情況是,如果客戶端持有的列表或者列表子集,其中節點都處於Active
狀態,能夠提供協調服務,那么客戶端訪問ZooKeeper
集群沒有任何問題。
另一種情況,客戶端持有ZooKeeper
集群節點列表或列表子集,如果列表中的某些節點因為故障退出了集群,如果客戶端再次連接這一類失效的節點,就無法獲取服務。
所以,我們在應用中使用ZooKeeper
集群時,一定要明確這一點,或者跳過無效的節點,或者重新尋找有效的節點繼續業務處理,或者檢查ZooKeeper
集群,使整個集群恢復正常。
配置導致的性能問題
如果設置Java
堆內存(Heap
)不合理,會導致ZooKeeper
內存不足,會在內存與文件系統之間進行數據交換,導致ZooKeeper
的性能極大地下降,從而可能會影響應用程序。
為了避免Swapping
問題的出現,主要考慮設置足夠的Java
堆內存,同時減少被操作系統和Cache
使用的內存,盡量避免在內存與文件系統之間發生數據交換,或者可以將交換限制在一定的范圍之內。
事務日志存儲設備性能
ZooKeeper
會同步事務到存儲設備,如果存儲設備不是專用的,而是和其他I/O
密集型應用共享同一磁盤,會導致ZooKeeper
的效率。因為客戶端請求ZNode
數據變更而發生的事務,ZooKeeper
會在響應之前將事務日志寫入存儲設備,如果存儲設備是專用的,那么整個服務以至外部應用都會獲得極大地性能提升。
ZNode
存儲大量數據導致性能問題
ZooKeeper
的設計初衷是,每個ZNode
只存放少量的同步數據,如果存儲了大量數據,導致ZooKeeper
每次節點發生變更時需要將事務寫入存儲設備,同時還要在集群內部復制傳播,這將導致不可避免的延遲和性能問題。
所以,如果需要與大量的數據相關,可以將大量數據存儲在其他設備中,而只是在ZooKeeper
中存儲一個簡單的映射,如指針、引用等等。
文章轉自:http://www.aboutyun.com/thread-7731-1-1.html