摘要:與此同時,小組也一同致力于項目,參與了很多動物命名的項目,其中有廣為人知的項目。主控服務器將所有更新操作序列化,利用協議將數據更新請求通知所有從屬服務器,保證更新操作。在術語下,節點被稱為。命名為的,由系統自動生成,用配額管理。
ZooKeeper 介紹
ZooKeeper(wiki,home,github) 是用于分布式應用的開源的分布式協調服務。通過暴露簡單的原語,分布式應用能在之上構建更高層的服務,如同步、配置管理和組成員管理等。在設計上易于編程開發,并且數據模型使用了熟知的文件系統目錄樹結構 [doc ]。
共識與 Paxos在介紹 ZooKeeper 之前,有必要了解下 Paxos 和 Chubby。2006 年 Google 在 OSDI 發表關于 Bigtable 和 Chubby 的兩篇會議論文,之后再在 2007 年 PODC 會議上發表了論文“Paxos Made Live”,介紹 Chubby 底層實現的共識(consensus)協議 Multi-Paxos,該協議對 Lamport 的原始 Paxos 算法做了改進,提高了運行效率 [ref ]。Chubby 作為鎖服務被 Google 應用在 GFS 和 Bigtable 中。受 Chubby 的影響,來自 Yahoo 研究院的 Benjamin Reed 和 Flavio Junqueira 等人開發了被業界稱為開源版的 Chubby 的 ZooKeeper(內部實現事實上稍有不同 [ref ]),底層的共識協議為 ZAB。Lamport 的 Paxos 算法出了名的難懂,如何讓算法更加可理解(understandable),便成了 Stanford 博士生 Diego Ongaro 的研究課題。Diego Ongaro 在 2014 年發表了介紹 Raft 算法的論文,“In search of an understandable consensus algorithm”。Raft 是可理解版的 Paxos,很快就成為解決共識問題的流行協議之一。這些類 Paxos 協議和 Paxos 系統之間的關系,如下 [Ailijiang2016 ]:
Google 的 Chubby 沒有開源,在云計算和大數據技術的風口下,Yahoo 開源的 ZooKeeper 便在工業界流行起來。ZooKeeper 重要的時間線如下:
2007 年 11 月 ZooKeeper 1.0 在 SourceForge 上發布 [ref ]
2008 年 6 月開始從 SourceForge 遷移到 Apache [ref ],在 10 月 Zookeeper 3.0 發布,并成為 Hadoop 的子項目 [ref1 ref2 ]
關于 ZooKeeper 名字的來源,Flavio Junqueira 和 Benjamin Reed 在介紹 ZooKeeper 的書中有如下闡述:
ZooKeeper 由雅虎研究院開發。我們小組在進行 ZooKeeper 的開發一段時間之后,開始推薦給其他小組,因此我們需要為我們的項目起一個名字。與此同時,小組也一同致力于 Hadoop 項目,參與了很多動物命名的項目,其中有廣為人知的 Apache Pig 項目(http://pig.apache.org)。我們在討論各種各樣的名字時,一位團隊成員提到我們不能再使用動物的名字了,因為我們的主管覺得這樣下去會覺得我們生活在動物園中。大家對此產生了共鳴,分布式系統就像一個動物園,混亂且難以管理,而 ZooKeeper 就是將這一切變得可控。體系結構
ZooKeeper 服務由若干臺服務器構成,其中的一臺通過 ZAB 原子廣播協議選舉作為主控服務器(leader),其他的作為從屬服務器(follower)。客戶端可以通過 TCP 協議連接任意一臺服務器。如果客戶端是讀操作請求,則任意一個服務器都可以直接響應請求;如果是更新數據操作(寫數據或者更新數據)。則只能由主控服務器來協調更新操作;如果客戶端連接的是從屬服務器,則從屬服務器會將更新據請求轉發到主控服務器,由其完成更新操作。主控服務器將所有更新操作序列化,利用 ZAB 協議將數據更新請求通知所有從屬服務器,ZAB 保證更新操作。
讀和寫操作,如下圖所示 [Haloi2015 ]:
ZooKeeper 的任意一臺服務器都可以響應客戶端的讀操作,這樣可以提高吞吐量。Chubby 在這點上與 ZooKeeper 不同,所有讀/寫操作都由主控服務器完成,從屬服務器只是為了提高整個協調系統的可用性,即主控服務器發生故障后能夠在從屬服務器中快速選舉出新的主控服務器。在帶來高吞吐量優勢的同時,ZooKeeper 這樣做也帶來潛在的問題:客戶端可能會讀到過期數據,因為即使主控服務器已經更新了某個內存數據,但是 ZAB 協議還未能將其廣播到從屬服務器。為了解決這一問題,在 ZooKeeper 的接口 API 函數中提供了 sync 操作,應用可以根據需要在讀數據前調用該操作,其含義是:接收到 sync 命令的從屬服務器從主控服務器同步狀態信息,保證兩者完全一致。這樣如果在讀操作前調用 sync 操作,則可以保證客戶端一定可以讀取到最新狀態的數據。
數據模型ZooKeeper 所提供的命名空間跟標準文件系統很相似。路徑中一系列元素是用斜杠(/)分隔的。每個節點在 ZooKeper 命名空間中是用路徑來識別的。在 ZooKeeper 術語下,節點被稱為 znode。默認每個 znode 最大只能存儲 1M 數據(可以通過配置參數修改),這與 Chubby 一樣是出于避免應用將協調系統當作存儲系統來用。znode 只能使用絕對路徑,相對路徑不能被 ZooKeeper 識別。znode 命名可以是任意 Unicode 字符。唯一的例外是,名稱"/zookeeper"。命名為"/zookeeper"的 znode,由 ZooKeeper 系統自動生成,用配額(quota)管理。
ZooKeeper 使用 安裝與配置ZooKeeper 安裝與啟動:
$ brew info zookeeper zookeeper: stable 3.4.10 (bottled), HEAD Centralized server for distributed coordination of services https://zookeeper.apache.org/ ... 省略 $ brew install zookeeper $ zkServer start # 啟動 $ zkServer stop # 終止 $ zkServer help ZooKeeper JMX enabled by default Using config: /usr/local/etc/zookeeper/zoo.cfg Usage: ./zkServer.sh {start|start-foreground|stop|restart|status|upgrade|print-cmd}
若不修改配置文件,默認是單機模式啟動。若要使用集群模式,需要修改 /usr/local/etc/zookeeper/zoo.cfg(默認路徑)。示例 zoo.cfg [doc ]:
tickTime=2000 dataDir=/var/lib/zookeeper clientPort=2181 initLimit=5 syncLimit=2 server.1=192.168.211.11:2888:3888 server.2=192.168.211.12:2888:3888 server.3=192.168.211.13:2888:3888
clientPort:客戶端連接 Zookeeper 服務器的端口,Zookeeper 會監聽這個端口,接受客戶端的訪問請求。
server.X=YYY:A:B
X:表示的服務器編號;
YYY:表示服務器的ip地址;
A:表示服務器節點間的通信端口,用于 follower 和 leader 節點的通信;
B:表示選舉端口,表示選舉新 leader 時服務器間相互通信的端口,當 leader 掛掉時,其余服務器會相互通信,選擇出新的 leader。
若想在單臺主機上試驗集群模式,可以將 YYY 都修改為 localhost,并且讓兩個端口 A:B 也相互不同(比如:2888:3888, 2889:3889, 2890:3890),即可實現偽集群模式。示例 zoo.cfg 如下 [doc ]:
server.1=localhost:2888:3888 server.2=localhost:2889:3889 server.3=localhost:2890:3890
zkCli 支持的全部命令:
$ zkCli help ZooKeeper -server host:port cmd args stat path [watch] set path data [version] ls path [watch] delquota [-n|-b] path ls2 path [watch] setAcl path acl setquota -n|-b val path history redo cmdno printwatches on|off delete path [version] sync path listquota path rmr path get path [watch] create [-s] [-e] path data acl addauth scheme auth quit getAcl path close connect host:port節點類型及其操作
Zookeeper 支持兩種類型節點:持久節點(persistent znode)和臨時節點(ephemeral znode)。持久節點不論客戶端會話情況,一直存在,只有當客戶端顯式調用刪除操作才會消失。而臨時節點則不同,會在客戶端會話結束或者發生故障的時候被 ZooKeeper 系統自動清除。另外,這兩種類型的節點都可以添加是否是順序(sequential)的特性,這樣就有了:持久順序節點和臨時順序節點。
(1) 持久節點(persistent znode)
使用 create 創建節點(默認持久節點),以及使用 get 查看該節點:
$ zkCli # 啟動客戶端 [zk: localhost:2181(CONNECTED) 1] create /zoo "hello zookeeper" Created /zoo [zk: localhost:2181(CONNECTED) 2] get /zoo hello zookeeper cZxid = 0x8d ctime = Thu Nov 08 20:42:55 CST 2017 mZxid = 0x8d mtime = Thu Nov 08 20:42:55 CST 2017 pZxid = 0x8d cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 15 numChildren = 0
create 創建子節點,以及使用 ls 查看全部子節點:
[zk: localhost:2181(CONNECTED) 3] create /zoo/duck "" Created /zoo/duck [zk: localhost:2181(CONNECTED) 4] create /zoo/goat "" Created /zoo/goat [zk: localhost:2181(CONNECTED) 5] create /zoo/cow "" Created /zoo/cow [zk: localhost:2181(CONNECTED) 6] ls /zoo [cow, goat, duck]
delete 刪除節點,以及使用 rmr 遞歸刪除:
[zk: localhost:2181(CONNECTED) 7] delete /zoo/duck [zk: localhost:2181(CONNECTED) 8] ls /zoo [cow, goat] [zk: localhost:2181(CONNECTED) 9] delete /zoo Node not empty: /zoo [zk: localhost:2181(CONNECTED) 10] rmr /zoo [zk: localhost:2181(CONNECTED) 11] ls /zoo Node does not exist: /zoo
(2) 臨時節點(ephemeral znode)
和持久節點不同,臨時節點不能創建子節點:
$ zkCli # 啟動第1個客戶端 [zk: localhost:2181(CONNECTED) 0] create -e /node "hello" Created /node [zk: localhost:2181(CONNECTED) 40] get /node hello cZxid = 0x97 ctime = Thu Nov 08 21:01:25 CST 2017 mZxid = 0x97 mtime = Thu Nov 08 21:01:25 CST 2017 pZxid = 0x97 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x161092a0ff30000 dataLength = 5 numChildren = 0 [zk: localhost:2181(CONNECTED) 1] create /node/child "" Ephemerals cannot have children: /node/child
臨時節點在客戶端會話結束或者發生故障的時候被 ZooKeeper 系統自動清除?,F在試驗下的針對臨時節點自動清除的監視:
$ zkCli # 啟動第2個客戶端 [zk: localhost:2181(CONNECTED) 0] create -e /node "hello" Node already exists: /node [zk: localhost:2181(CONNECTED) 1] stat /node true cZxid = 0x97 ctime = Thu Nov 08 21:01:25 CST 2017 mZxid = 0x97 mtime = Thu Nov 08 21:01:25 CST 2017 pZxid = 0x97 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x161092a0ff30000 dataLength = 5 numChildren = 0
若客戶端1,退出 quit 或崩潰,客戶端2將收到監視事件:
[zk: localhost:2181(CONNECTED) 2] WATCHER:: WatchedEvent state:SyncConnected type:NodeDeleted path:/node
(3) 順序節點(sequential znode)
順序節點在其創建時 ZooKeeper 會自動在 znode 名稱上附加上順序編號。順序編號,由父 znode 維護,并且單調遞增。順序編號,由 4 字節的有符號整數組成,并被格式化為 0 填充的 10 位數字。
[zk: localhost:2181(CONNECTED) 1] create /test "" Created /test [zk: localhost:2181(CONNECTED) 2] create -s /test/seq "" Created /test/seq0000000000 [zk: localhost:2181(CONNECTED) 3] create -s /test/seq "" Created /test/seq0000000001 [zk: localhost:2181(CONNECTED) 4] create -s /test/seq "" Created /test/seq0000000002 [zk: localhost:2181(CONNECTED) 5] ls /test [seq0000000000, seq0000000001, seq0000000002]客戶端 API
ZooKeeper 提供的主要 znode 操作 API 如下表所示:
API 操作 | 描述 | CLI 命令 |
---|---|---|
create | 創建 znode | create |
delete | 刪除 znode | delete/rmr/delquota |
exists | 檢查 znode 是否存在 | stat |
getChildren | 讀取 znode 全部的子節點 | ls/ls2 |
getData | 讀取 znode 數據 | get/listquota |
setData | 設置 znode 數據 | set/setquota |
getACL | 讀取 znode 的 ACL | getACL |
setACL | 設置 znode 的 ACL | setACL |
sync | 同步 | sync |
Java 的 ZooKeeper 類實現了上述提供的 API。
Zookeeper 底層是 Java 實現,zkCli 命令行工具底層也是 Java 實現,對應的 Java 實現類為 org.apache.zookeeper.ZooKeeperMain [src1 src2 ]。ZooKeeper 3.5.x 下,CLI 命令與底層實現 API 對應關系:
命名 CLI | Java API (ZooKeeper 類) |
---|---|
addauth scheme auth | public void addAuthInfo(String scheme, byte[] auth) |
close | public void close() |
create [-s] [-e] path data acl | public String create(final String path, byte data[], List |
delete path [version] | public void delete(String path, int version) |
delquota [-n|-b] path | public void delete(String path, int version) |
get path [watch] | public byte[] getData(String path, boolean watch, Stat stat) |
getAcl path | public List |
listquota path | public byte[] getData(String path, boolean watch, Stat stat) |
ls path [watch] | public List |
ls2 path [watch] | - |
quit | public void close() |
rmr path | public void delete(final String path, int version) |
set path data [version] | public Stat setData(String path, byte[] data, int version) |
setAcl path acl | public Stat setACL(final String path, List |
setquota -n|-b val path | public Stat setData(String path, byte[] data, int version) |
stat path [watch] | public Stat exists(String path, boolean watch) |
sync path | public void sync(String path, AsyncCallback.VoidCallback cb, Object ctx) |
ZooKeeper 提供了處理變化的重要機制一一監視點(watch)。通過監視點,客戶端可以對指定的 znode 節點注冊一個通知請求,在發生變化時就會收到一個單次的通知。當應用程序注冊了一個監視點來接收通知,匹配該監視點條件的第一個事件會觸發監視點的通知,并且最多只觸發一次。例如,當 znode 節點也被刪除,客戶端需要知道該變化,客戶端在 /z 節點執行 exists 操作并設置監視點標志位,等待通知,客戶端會以回調函數的形式收到通知。
ZooKeeper 的 API 中的讀操作:getData、getChildren 和 exists,均可以選擇在讀取的 znode 節點上設置監視點。使用監視點機制,我們需要實現 Watcher 接口類,該接口唯一方法為 process:
void process(WatchedEvent event)
WatchedEvent 數據結構包括以下信息:
ZooKeeper會話狀態(KeeperState):Disconnected、SyncConnected、AuthFailed、ConnectedReadOnly 、SaslAuthenticated、Expired。
事件類型(EventType):NodeCreated 、NodeDeleted 、NodeDataChanged、NodeChildrenChanged 和 None 。
若事件類型不是 None,還包括 znode 路徑。
若收到 WatchedEvent, 在 zkCli 中會輸出類似如下結果:
WatchedEvent state:SyncConnected type:NodeDeleted path:/node
監視點有兩種類型:數據監視點和子節點監視點。創建、刪除或設置一個 znode 節點的數據都會觸發數據監視點,exists 和 getData 這兩個操作可以設置數據監視點。只有 getChildren 操作可以設置子節點監視點,這種監視點只有在 znode 子節點創建或刪除時才被觸發。對于每種事件類型,我們通過以下調用設置監視點:
NodeCreated
???通過 exists 調用設置一個監視點。
NodeDeleted
???通過 exists 或 getData 調用設置監視點。
NodeDataChanged
???通過 exists 或getData 調用設置監視點。
NodeChildrenChanged
???通過 getChildren 調用設置監視點。
在 Java 下使用 ZooKeeper 需要先添加如下 maven 依賴:
org.apache.zookeeper zookeeper 3.4.11 pom
ZookeeperDemo 示例,展示了建立連接會話,以及對 znode 的創建、讀取、修改、刪除和設置監視點等操作:
import java.io.IOException; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; public class ZookeeperDemo { public static void main(String[] args) throws KeeperException, InterruptedException, IOException { // 創建服務器連接 ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 100, new Watcher() { // 監控所有被觸發的事件 public void process(WatchedEvent event) { System.out.printf("WatchedEvent state:%s type:%s path:%s ", event.getState(), event.getType(), event.getPath()); } }); // 創建節點 zk.create("/zoo", "hello ZooKeeper".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // 讀取節點數據 Stat stat = new Stat(); System.out.println(new String(zk.getData("/zoo", false, stat))); printStat(stat); // 創建子節點 zk.create("/zoo/duck", "hello duck".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk.create("/zoo/goat", "hello goat".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk.create("/zoo/cow", "hello cow".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // 讀取子節點列表,并設置監視點 System.out.println(zk.getChildren("/zoo", true)); // 讀取子節點數據,并設置監視點 System.out.println(new String(zk.getData("/zoo/duck", true, null))); // 修改子節點數據 zk.setData("/zoo/duck", "hi duck".getBytes(), -1); // 讀取修改后的子節點數據 System.out.println(new String(zk.getData("/zoo/duck", true, null))); // 刪除子節點 zk.delete("/zoo/duck", -1); zk.delete("/zoo/goat", -1); zk.delete("/zoo/cow", -1); // 刪除父節點 zk.delete("/zoo", -1); // 關閉連接 zk.close(); } private static void printStat(Stat stat) { System.out.println("cZxid = 0x" + Long.toHexString(stat.getCzxid())); System.out.println("ctime = " + DateFormatUtils.format(stat.getCtime(), "yyyy-MM-dd HH:mm:ss")); System.out.println("mZxid = 0x" + Long.toHexString(stat.getMzxid())); System.out.println("mtime = " + DateFormatUtils.format(stat.getMtime(), "yyyy-MM-dd HH:mm:ss")); System.out.println("pZxid = 0x" + Long.toHexString(stat.getPzxid())); System.out.println("cversion = " + stat.getCversion()); System.out.println("dataVersion = " + stat.getVersion()); System.out.println("aclVersion = " + stat.getAversion()); System.out.println("ephemeralOwner = 0x" + Long.toHexString(stat.getEphemeralOwner())); System.out.println("dataLength = " + stat.getDataLength()); System.out.println("numChildren = " + stat.getNumChildren()); } }
輸出結果:
WatchedEvent state:SyncConnected type:None path:null hello ZooKeeper cZxid = 0x1e1 ctime = 2017-11-20 12:18:36 mZxid = 0x1e1 mtime = 2017-11-20 12:18:36 pZxid = 0x1e1 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 15 numChildren = 0 [cow, goat, duck] hello duck WatchedEvent state:SyncConnected type:NodeDataChanged path:/zoo/duck hi duck WatchedEvent state:SyncConnected type:NodeDeleted path:/zoo/duck WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/zooZooInspector
ZooInspector 是 ZooKeeper 3.3.0 開始官方提供的可視化查看和編輯 ZooKeeper 實例的工具 [ZOOKEEPER-678 ]。源碼位于目錄 src/contrib/zooinspector 下,GitHub 地址為:link??梢愿鶕?README.txt 的說明運行使用。或者可以直接用 ZOOKEEPER-678 下提供的可執行 jar 包。
參考資料官方文檔:ZooKeeper http://zookeeper.apache.org/d...
2010-11 許令波:分布式服務框架 Zookeeper https://www.ibm.com/developer...
ZooKeeper:分布式過程協同技術詳解,Benjamin Reed & Flavio Junqueira,2013,豆瓣
Apache ZooKeeper Essentials, Haloi 2015,豆瓣
從Paxos到Zookeeper,阿里倪超 2015,豆瓣
大數據日知錄:架構與算法,張俊林 2014,第5章 分布式協調系統,豆瓣
2010,Patrick Hunt, Mahadev Konar, Flavio Paiva Junqueira, Benjamin Reed: ZooKeeper: Wait-free Coordination for Internet-scale Systems. USENIX ATC 2010,dblp,msa,usenix
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74712.html
摘要:可靠性一旦數據更新成功,將一直保持,直到新的更新。這是一種主動的分布式數據結構,能夠在外部情況發生變化時候主動修改數據項狀態的數據機構。如果監視節點狀態發生變化,則跳轉到第步,繼續進行后續的操作,直到退出鎖競爭。 題外話:從字面上來看,ZooKeeper表示動物園管理員,而Hadoop生態系統中,許多項目的Logo都采用了動物,比如Hadoop采用了大象的形象,所以可以ZooKeepe...
摘要:相關概念協議高級消息隊列協議是一個標準開放的應用層的消息中間件協議。可以用命令與不同,不是線程安全的。手動提交執行相關邏輯提交注意點將寫成單例模式,有助于減少端占用的資源。自身是線程安全的類,只要封裝得當就能最恰當的發揮好的作用。 本文使用的Kafka版本0.11 先思考些問題: 我想分析一下用戶行為(pageviews),以便我能設計出更好的廣告位 我想對用戶的搜索關鍵詞進行統計,...
閱讀 1928·2021-11-24 09:39
閱讀 2146·2021-09-22 15:50
閱讀 2028·2021-09-22 14:57
閱讀 713·2021-07-28 00:13
閱讀 1077·2019-08-30 15:54
閱讀 2369·2019-08-30 15:52
閱讀 2695·2019-08-30 13:07
閱讀 3796·2019-08-30 11:27