ZooKeeper私人學習筆記

俗話說“好記性不如爛筆頭”,編程的海洋如此的浩大,養成做筆記的習慣是成功的一步!


此筆記主要是ZooKeeper3.4.9版本的筆記,並且筆記都是博主自己一字一字編寫和記錄,有錯誤的地方歡迎大家指正。




一、基礎知識:
1、ZooKeeper是一個分佈式的,開放源碼的分佈式應用程序協調服務,它包含一個簡單的原語集,分佈式應用程序可以基於它實現同步
  服務,配置維護和命名服務等。Zookeeper是hadoop的一個子項目,由於功能卓越,現已是apache的一個頂級子項目,應用於各種分佈
  式場景。官方網站:http://zookeeper.apache.org。
  
  
2、在分佈式應用中,由於工程師不能很好地使用鎖機制,以及基於消息的協調機制不適合在某些應用中使用,因此需要有一種可靠的、
  可擴展的、分佈式的、可配置的協調機制來統一系統的狀態,Zookeeper的目的就在於此。Zookeeper的節點和數據操作,都能保證
  在併發情況下的同步性和安全性,因此Zookeeper經常用於分佈式鎖的操作,同時也用於集羣數據共享的情形。
  
  
3、Zookeeper是通過的連接是長連接的,通過定時的發送心跳來檢測服務器的有效性,當檢測到服務器故障後,會直接操作該服務器
  在Zookeeper上的節點狀態,當節點發生變化時,會通知對當前節點進行檢測的客戶端(即觀察者模式)。
  
  
  
4、Zookeeper的設計特點:
(1)最終一致性:client不論連接到哪個Server,展示給它都是同一個視圖,這是zookeeper最重要的性能。


(2)可靠性:具有簡單、健壯、良好的性能,如果消息m被到一臺服務器接受,那麼它將被所有的服務器接受。


(3)實時性:Zookeeper保證客戶端將在一個時間間隔範圍內獲得服務器的更新信息,或者服務器失效的信息。
但由於網絡延時等原因,Zookeeper不能保證兩個客戶端能同時得到剛更新的數據,如果需要最新數據,
應該在讀數據之前調用sync()接口。


(4)等待無關(wait-free):慢的或者失效的client不得干預快速的client的請求,使得每個client都能有效的等待。


(5)原子性:更新只能成功或者失敗,沒有中間狀態。


(6)順序性:包括全局有序和偏序兩種:全局有序是指如果在一臺服務器上消息a在消息b前發佈,則在所有Server上消息a
 都將在消息b前被髮布;偏序是指如果一個消息b在消息a後被同一個發送者發佈,a必將排在b前面。
 
 
 
5、Zookeeper集羣的master選舉,必須要要超過半數以上的服務器同意,故Zookeeper集羣的服務器初始數量應爲2n+1,即爲奇數臺。
例如:
如果有2臺服務器A和B,假設A要選舉爲master服務器,但此時只有一臺B服務器,即使B服務器同意,也只有1臺服務器,
因爲不滿足n/2 + 1的數量,即 1 < 2 沒有超過半數,因此會出現選舉失敗。



6、Zookeeper的使用場景:
(1)統一命名服務。
分佈式應用中,通常需要有一套完整的命名規則,既能夠產生唯一的名稱又便於人識別和記住,Name Service 已經是 
Zookeeper 內置的功能,通過調用API的create創建子節點。

(2)配置管理。
配置的管理在分佈式應用環境中很常見,例如同一個應用系統需要多臺 PC Server 運行,如果要修改這些相同的配置
項,那麼就必須同時修改每臺運行這個應用系統的 PC Server。像這樣的配置信息完全可以交給 Zookeeper 來管理,將配
置信息保存在 Zookeeper 的某個目錄節點中,然後將所有需要修改的應用機器監控配置信息的狀態,一旦配置信息發生變
化,每臺應用機器就會收到 Zookeeper 的通知,然後從 Zookeeper 獲取新的配置信息應用到系統中。

(3)集羣管理。
它們的實現方式都是在 Zookeeper 上創建一個 EPHEMERAL 類型的目錄節點,然後每個 Server 在它們創建目錄節點的
父目錄節點上調用 getChildren(String path, boolean watch) 方法並設置 watch 爲 true,由於是 EPHEMERAL 目錄節點,
當創建它的 Server 死去,這個目錄節點也隨之被刪除,所以 Children 將會變化,這時 getChildren上的 Watch 將會被
調用,所以其它 Server 就知道已經有某臺 Server 死去了。新增 Server 也是同樣的原理。

(4)共享鎖。
實現方式也是需要獲得鎖的 Server 創建一個 EPHEMERAL_SEQUENTIAL 目錄節點,然後調用 getChildren方法獲取當前
的目錄節點列表中最小的目錄節點是不是就是自己創建的目錄節點,如果正是自己創建的,那麼它就獲得了這個鎖,如果不
是那麼它就調用 exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到自己創建
的節點是列表中最小編號的目錄節點,從而獲得鎖,釋放鎖很簡單,只要刪除前面它自己所創建的目錄節點就行了。

(5)隊列管理。
形式一:當一個隊列的成員都聚齊時,這個隊列纔可用,否則一直等待所有成員到達,這種是同步隊列。
形式二:隊列按照 FIFO 方式進行入隊和出隊操作,例如實現生產者和消費者模型。


 
7、當客戶端watch某個znode節點的時候,某個節點被改變時(例如數據改變或刪除或子節點有變化等),Zookeeper會自動通知watch
  的客戶端,然後清空掉此節點下所有的watch的客戶端。因此,客戶端向繼續監聽某個znode節點,則必須重新進行監視watch此節點。
  
  此模式其實就是觀察者模式,當被觀察的對象發生改變時,通知觀察者。
  




8、Zookeeper服務器的使用需要注意的幾個事項:
(1)watch監視是一次性的,再次監視需要重新設置監視節點。

(2)如果監視znode節點的客戶端失去連接了,那麼znode被改變後客戶端是不會收到通知的,
即使後面客戶端重新連接了Zookeeper。
 
(3)在刪除znode節點的時候,只允許在空子節點的情況下刪除,如果當前節點下有子節點,那麼是不允許刪除的。

(4)Zookeeper有種臨時節點Ephemeral節點,在此連接斷開後,就會被自動清除。因此臨時節點不允許有子節點。

(5)Zookeeper的節點存儲的數據不能大於1M,本身的設計就不是用來存儲大數據,因此需要避免節點下的數據過大。


(6)相同的路徑下,不允許有相同名稱的節點存在,如果創建相同名字的節點會在創建時報錯。如果基本名稱都一樣的,
可以創建序列節點Sequence節點,Zookeeper會自動在基本名稱後面添加一個按順序的數字標識。
 
 
 

9、每個znode節點的數據結構如下:
czxid:創建此節點時的zxid(Zookeeper Transition ID)
The zxid of the change that caused this znode to be created.

mzxid:最後更改此節點的zxid。
The zxid of the change that last modified this znode.

pzxid:最好更改此節點或子節點時的zxid。
The zxid of the change that last modified children of this znode.

ctime:創建此節點時的時間。
The time in milliseconds from epoch when this znode was created.

mtime:最終修改此節點的時間。
The time in milliseconds from epoch when this znode was last modified.

dataVersion:當前節點數據版本號,數據被修改版本號就會自增1。
The number of changes to the data of this znode.

cversion:當前節點下的子節點被修改時的版本號,子節點新增或刪除時會自增1。
The number of changes to the children of this znode.

aclVersion:當前節點的ACL(訪問控制列表)被修改時的版本號,每次修改會自增1。
The number of changes to the ACL of this znode.

ephemeralOwner:如果當前節點是臨時節點,則存儲session id,如果不是臨時節點則爲0.
he session id of the owner of this znode if the znode is an ephemeral node. 
If it is not an ephemeral node, it will be zero.

dataLength:當前節點下保存的數據長度。
The length of the data field of this znode.

numChildren:當前節點下的子節點個數。
The number of children of this znode.
 


10、Zookeeper的watch機制:
(1)服務端維護兩個watch列表,一個是當前節點與數據watch列表,另外一個是子節點watch列表。
注意:
當前節點與數據watch列表:是當節點改變或者節點數據改變時,都會觸發的列表。
子節點watch列表:是當子節點改變時觸發,而子節點的數據改變時是不會觸發的。


(2)設置監聽者,即指定監聽watch的znod節點的方式:
getData()和exists()設置當前節點與數據Watch,getChildren()設置子節點Watch。


(3)觸發機制,即會觸發watch的方式:
setData()觸發內容Watch。即觸發當前節點與數據watch列表。
create()觸發其父節點的子節點Watch。子節點watch列表觸發。
delete()同時觸發父節點的子節點Watch和當前節點與數據Watch。當前節點與數據watch列表和子節點watch列表都有觸發。



11、Zookeeper的ACL(訪問控制列表):
(1)ACL的權限分爲五類:
CREATE: 創建權限,允許在該節點下創建子節點。you can create a child node.

READ:讀權限,允許讀取該節點數據和查詢他的所屬子節點。 you can get data from a node and list its children.

WRITE: 寫權限,允許在該節點下修改data數據。you can set data for a node.

DELETE: 刪除權限,允許刪除他的所屬子節點。you can delete a child node.

ADMIN: 管理權限,允許再該節點下設置ACL。you can set permissions.


注意:CREATE和DELETE都是他所屬的子節點進行權限控制的,並不是針對當前節點。
     即任何人都可以刪除當前節點,如果你知道節點路徑的話。


(2)ACL的權限是不遞歸的。例如:假設給一個節點設置了讀權限的控制,一個用戶即使沒有讀取當前節點的權限,
  但是如果知道當前節點下的子節點路徑,依舊可以讀取子節點的數據,父節點的權限不會遞歸到子節點。
  
  
  
(3)ACL的控制策略有如下幾類:
world: 它下面只有一個id, 叫anyone, world:anyone代表任何人。

auth: 它不需要id, 只要是通過authentication的user都有權限,也就是說默認採用的username:password就是
     當前用戶認證使用的用戶名和密碼,如果當前用戶沒有認證過,使用auth策略會報錯。

digest: 它對應的id爲username:BASE64(SHA1(password)),它需要先通過username:password形式的進行認證。

ip: 它對應的id爲客戶機的IP地址,設置的時候可以設置一個ip段,比如ip:192.168.1.0/16, 表示匹配前16個bit的IP段。

x509: 客戶端使用X500的規則來認證。


提示:比較常用的是digest和auth的形式。



 
 
 
二、安裝部署:
1、Zookeeper是用Java語言實現的,因此必須要先安裝JDK。

2、Zookeeper單臺服務器的安裝(基於Linux系統):
步驟一:解壓zookeeper-3.4.9.tar.gz目錄,執行命令 tar -zxvf zookeeper-3.4.9.tar.gz -C /usr/user


步驟二:進入加壓後的目錄/usr/user/zookeeper-3.4.9 ,在該目錄下創建data和log目錄用於存放數據和日誌,
執行命令 mkdir data log


步驟三:在當前Zookeeper目錄下,進入conf目錄進行配置。新建zoo.cfg文件(Zookeeper默認會加載此配置),然後輸入如下配置:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/users/zookeeper/data
dataLogDir=/usr/users/zookeeper/log
clientPort=2181


 

步驟四:在當前Zookeeper目錄下,進入bin目錄。執行命令來操作Zookeeper服務。
sh  zkServer.sh start #啓動Zookeeper服務
sh  zkServer.sh stop #停止Zookeeper服務
sh  zkServer.sh status #查看Zookeeper服務的運行狀態。



步驟五:通過Zookeeper自帶的客戶端操作Zookeeper服務。執行命令  sh zkCli.sh -server localhost:2181 
如果是連接本地Zookeeper服務,並且使用的是默認的2181端口,則可以簡寫 sh zkCli.sh 





3、Zookeeper的集羣安裝(基於Linux系統):
因爲Zookeeper本身就是爲集羣提供服務的,在設計時就考慮Zookeeper的集羣,因此Zookeeper的集羣非常簡單。基於單臺服務器
的安裝後,增加如下步驟:假設有三臺服務器 192.168.1.2:2181、192.168.1.3:2181、192.168.1.4:2181

續步驟一:在當前Zookeeper目錄下,進入data目錄,創建myid文件(用於給當前Zookeeper服務器添加id標識,範圍爲1~255)。
  此處設置標識如下(myid只需輸入數字)
  192.168.1.2服務器的id爲 1
  192.168.1.3服務器的id爲 2
  192.168.1.4服務器的id爲 3
  
  
  
繼步驟二:在conf/zoo.cfg配置文件下,添加如下配置信息:
server.1=192.168.1.2:2888:3888
server.2=192.168.1.3:2888:3888
server.3= 92.168.1.4:2888:3888

#1,2,3分別是每個服務器的id,後面是對應的ip地址,2888:3888是可以用於Zookeeper服務器之間內部通信使用的端口。



注意:使用Zookeeper集羣時,注意防火牆配置,可能會引起Zookeeper無法探測到其他服務器,導致啓動失敗。




三、java 連接Zookeeper的使用筆記:
1、java連接Zookeeper的開源框架有三種:
第一種是Zookeeper官方提供的原生java api。

第二種是由datameer的工程師開發的zkClient,源碼在github上可下載。修復了原生java api的一些bug和簡化api操作。

第三種是curator框架,也是apache開發的。curator的api更簡單,更能更強大,是最受歡迎的java版Zookeeper連接框架。

注意:本筆記默認是使用官方提供的原生api來連接Zookeeper。




2、zookeeper-3.4.9.jar包是java連接Zookeeper服務端的原生jar包,依賴於slfj日誌門面框架的jar包,需要同時引入,
  具體的jar包在lib_jar目錄下。



3、原生api使用了java的非阻塞NIO來連接,並且是開啓新的線程來去連接服務端的。因此,主線程必須要監控連接成功事件,
  在連接成功後纔開始操作Zookeeper,以免拋出異常。
  
  提示:可以藉助JDK併發庫的CountDownLatch 對象來實現主線程的阻塞,直到連接成功。
  
  代碼示例:
final CountDownLatch countDownLatch = new CountDownLatch(1);
//創建Zookeeper的核心對象,此對象在創建時就開啓新的線程去連接服務端。
ZooKeeper zk = new ZooKeeper("10.17.2.7:2181", 3000, new Watcher() {
@Override
public void process(WatchedEvent event) {
//連接成功的狀態,進行通知。
if (event.getType() == EventType.None && event.getState() == KeeperState.SyncConnected) {
countDownLatch.countDown();
}
}
});
//等待連接成功。
countDownLatch.await();
//連接成功,開始執行對zk操作的業務邏輯。
System.out.println("ZooKeeper 連接成功!");
  
  
  
  
4、原生api可以定製默認的watcher監視者,就是在創建核心操作類ZooKeeper的時候,在構造方法中指定默認的watcher,
  此方法的所有api中凡是通過boolean值來指定是否監視的,如果爲true則都是使用默認的watcher進行監視。如果爲false,
  則不進行監視。
  
  例如:zk.getData()、zk.getChildren()等方法。



5、Zookeeper服務器是支持事務的,因此java版的原生api也支持事務的操作。實例代碼如下:
//創建連接
ZooKeeper zk = new ZooKeeper("10.17.2.7:2181", 3000, null);
//獲得事務對象
Transaction ts = zk.transaction();
//通過事務對象來操作數據。如果是用zk對象來操作數據,那麼是不在此事務範圍內的。如zk.create()操作是不受事務影響的。
ts.create("/java/a7", "節點a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//必須提交事務,否則數據修改無效。
ts.commit();
zk.close();





6、使用Zookeeper獲得分佈式鎖的基本思想:
  先創建好一個獲得鎖的父節點,然後每個線程在該父節點下創建自己的EPHEMERAL_SEQUENTIAL節點(臨時隊列節點),然後獲取
該父節點下的所有子節點,使用TreeSet進行自然排序,然後獲取首個元素(即序列最小的元素),判斷最小元素的名稱是否是當前
創建的最小元素名稱,如果是則表名當前線程獲取了鎖,可以執行業務邏輯。如果不是,則獲取此元素前面的一個元素,然後進行
watch監視,當監視節點被刪除或,獲取獲取所有的節點進行判斷,重複執行此邏輯,直到當前最小元素爲當前線程創建的最小元素。

注意:當獲取鎖的線程操作完成後,必須刪除當前節點,以便讓後續監視者執行監視邏輯的方法,從而讓監視者得到鎖。
  
說明:每個線程創建的節點必須是EPHEMERAL_SEQUENTIAL節點。因爲臨時節點會在當前連接斷開後會自動刪除,這樣可以在當前線程
 的客戶端宕機後,及時的處理掉他所擁有的節點,避免線程死鎖。同時,創建隊列類型的節點,在創建時服務端會按創建順序
 在節點名稱後面加一串序列數字,可以通過此數字來確定獲取鎖的節點是哪個。
 
 
 
附加:除了使用Zookeeper獲取分佈式鎖以外,還可以使用redis來獲取,但Zookeeper獲取分佈式鎖更方便。
 redis獲取分佈式鎖的基本思想:藉助setnx和getset命令來實現。首先通過setnx存儲一個時間戳值,此時間戳表示鎖的有效
 時間。如果設置成功,則表名是獲取鎖,當前線程可獲得鎖。如果設置失敗,則開始每隔一段時間循環讀取。使用get讀取當
 前key的value值,value值存的是鎖的有效時間,判斷value值是否小於當前時間,如果是,則說明鎖已經失效,則根據當前時
 間計算出鎖的有效時間,然後使用getset方法,更新舊的鎖時間,如果返回的舊值與之前get讀取的值一致,則說明獲取鎖成
 功。如果不一致,則說明被其他線程先執行getset操作了,則獲取循環嘗試獲取鎖。
 
 
 setnx命令:是當key不存在時纔會設置成功並返回1,否則設置失敗並返回0。
 getset命令:更新當前key的value值,並返回舊的value值,如果當前沒有舊值,則返回nil。
 get命令:根據key讀取value值,如果沒有則返回nil。





/***************************************************************附加**********************************************************/
附加1、使用zkCli.sh 腳本進入Zookeeper的客戶端,可以使用的命令有:
[zkshell: 0] help
ZooKeeper host:port cmd args
get path [watch]
ls path [watch]
set path data [version]
delquota [-n|-b] path
quit
printwatches on|off
create path data acl
stat path [watch]
listquota path
history
setAcl path acl
getAcl path
sync path
redo cmdno
addauth scheme auth
delete path [version]
deleteall path
setquota -n|-b val path













  



發佈了48 篇原創文章 · 獲贊 10 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章