什么是 ZooKeeper?
ZooKeeper 是一個(gè)分布式的巧号,開(kāi)放源碼的分布式應(yīng)用程序協(xié)同服務(wù)。ZooKeeper 的設(shè)計(jì)目標(biāo)是將那些復(fù)雜且容易出錯(cuò)的分布式一致性服務(wù)封裝起來(lái),構(gòu)成一個(gè)高效可靠的原語(yǔ)集颜曾,并以一系列簡(jiǎn)單易用的接口提供給用戶使用。
ZooKeeper 發(fā)展歷史
ZooKeeper 最早起源于雅虎研究院的一個(gè)研究小組秉剑。在當(dāng)時(shí)泛豪,研究人員發(fā)現(xiàn),在雅虎內(nèi)部很多大型系統(tǒng)基本都需要依賴一個(gè)類似的系統(tǒng)來(lái)進(jìn)行分布式協(xié)同侦鹏,但是這些系統(tǒng)往往都存在分布式單點(diǎn)問(wèn)題诡曙。
所以,雅虎的開(kāi)發(fā)人員就開(kāi)發(fā)了一個(gè)通用的無(wú)單點(diǎn)問(wèn)題的分布式協(xié)調(diào)框架略水,這就是 ZooKeeper价卤。ZooKeeper 之后在開(kāi)源界被大量使用,下面列出了 3 個(gè)著名開(kāi)源項(xiàng)目是如何使用 ZooKeeper:
- Hadoop:使用 ZooKeeper 做 Namenode 的高可用渊涝。
- HBase:保證集群中只有一個(gè) master慎璧,保存 hbase:meta 表的位置床嫌,保存集群中的 RegionServer 列表。
- Kafka:集群成員管理炸卑,controller 節(jié)點(diǎn)選舉既鞠。
ZooKeeper 應(yīng)用場(chǎng)景
很多分布式協(xié)調(diào)服務(wù)都可以用 ZooKeeper 來(lái)做,其中典型應(yīng)用場(chǎng)景如下:
- 配置管理(configuration management):如果我們做普通的 Java 應(yīng)用盖文,一般配置項(xiàng)就是一個(gè)本地的配置文件嘱蛋,如果是微服務(wù)系統(tǒng),各個(gè)獨(dú)立服務(wù)都要使用集中化的配置管理五续,這個(gè)時(shí)候就需要 ZooKeeper洒敏。
- DNS 服務(wù)
- 組成員管理(group membership):比如上面講到的 HBase 其實(shí)就是用來(lái)做集群的組成員管理。
- 各種分布式鎖
ZooKeeper 適用于存儲(chǔ)和協(xié)同相關(guān)的關(guān)鍵數(shù)據(jù)疙驾,不適合用于大數(shù)據(jù)量存儲(chǔ)凶伙。如果要存 KV 或者大量的業(yè)務(wù)數(shù)據(jù),還是要用數(shù)據(jù)庫(kù)或者其他 NoSql 來(lái)做它碎。
為什么 ZooKeeper 不適合大數(shù)據(jù)量存儲(chǔ)呢函荣?主要有以下兩個(gè)原因:
- 設(shè)計(jì)方面:ZooKeeper 需要把所有的數(shù)據(jù)(它的 data tree)加載到內(nèi)存中。這就決定了ZooKeeper 存儲(chǔ)的數(shù)據(jù)量受內(nèi)存的限制扳肛。這一點(diǎn) ZooKeeper 和 Redis 比較像傻挂。一般的數(shù)據(jù)庫(kù)系統(tǒng)例如 MySQL(使用 InnoDB 存儲(chǔ)引擎的話)可以存儲(chǔ)大于內(nèi)存的數(shù)據(jù),這是因?yàn)?InnoDB 是基于 B-Tree 的存儲(chǔ)引擎挖息。B-tree 存儲(chǔ)引擎和 LSM 存儲(chǔ)引擎都可以存儲(chǔ)大于內(nèi)存的數(shù)據(jù)量金拒。
- 工程方面:ZooKeeper 的設(shè)計(jì)目標(biāo)是為協(xié)同服務(wù)提供數(shù)據(jù)存儲(chǔ),數(shù)據(jù)的高可用性和性能是最重要的系統(tǒng)指標(biāo)套腹,處理大數(shù)量不是 ZooKeeper 的首要目標(biāo)绪抛。因此,ZooKeeper 不會(huì)對(duì)大數(shù)量存儲(chǔ)做太多工程上的優(yōu)化电禀。
ZooKeeper 服務(wù)的使用
要使用 ZooKeeper 服務(wù)幢码,首先我們的應(yīng)用要引入 ZooKeeper 的客戶端庫(kù),然后我們客戶端庫(kù)和 ZooKeeper 集群來(lái)進(jìn)行網(wǎng)絡(luò)通信來(lái)使用 ZooKeeper 的服務(wù)尖飞,本質(zhì)上是 Client-Server 的架構(gòu)症副,我們的應(yīng)用作為一個(gè)客戶端來(lái)調(diào)用 ZooKeeper Server 端的服務(wù)。
ZooKeeper 數(shù)據(jù)模型
ZooKeeper 的數(shù)據(jù)模型是層次模型葫松。層次模型常見(jiàn)于文件系統(tǒng)瓦糕。層次模型和 key-value 模型是兩種主流的數(shù)據(jù)模型底洗。ZooKeeper 使用文件系統(tǒng)模型主要基于以下兩點(diǎn)考慮:
- 文件系統(tǒng)的樹(shù)形結(jié)構(gòu)便于表達(dá)數(shù)據(jù)之間的層次關(guān)系腋么。
- 文件系統(tǒng)的樹(shù)形結(jié)構(gòu)便于為不同的應(yīng)用分配獨(dú)立的命名空間(namespace)。
ZooKeeper 的層次模型稱作 data tree亥揖。Data tree 的每個(gè)節(jié)點(diǎn)叫做 znode珊擂。不同于文件系統(tǒng)圣勒,每個(gè)節(jié)點(diǎn)都可以保存數(shù)據(jù)。每個(gè)節(jié)點(diǎn)都有一個(gè)版本(version)摧扇,版本從 0 開(kāi)始計(jì)數(shù)圣贸。
如上圖所示的 data tree 中有兩個(gè)子樹(shù),一個(gè)用于應(yīng)用 1(/app1)和另一個(gè)用于應(yīng)用 2(/app2)扛稽。
應(yīng)用 1 的子樹(shù)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的組成員協(xié)議:每個(gè)客戶端進(jìn)程 pi 創(chuàng)建一個(gè) znode p_i 在 /app1 下吁峻,只要 /app1/p_i 存在就代表進(jìn)程 pi 在正常運(yùn)行。
data tree 接口
ZooKeeper 對(duì)外提供一個(gè)用來(lái)訪問(wèn) data tree的簡(jiǎn)化文件系統(tǒng) API:
- 使用 UNIX 風(fēng)格的路徑名來(lái)定位 znode,例如 /A/X 表示 znode A 的子節(jié)點(diǎn) X在张。
- znode 的數(shù)據(jù)只支持全量寫(xiě)入和讀取用含,沒(méi)有像通用文件系統(tǒng)那樣支持部分寫(xiě)入和讀取。
- data tree 的所有 API 都是 wait-free 的帮匾,正在執(zhí)行中的 API 調(diào)用不會(huì)影響其他 API 的完成啄骇。
- data tree 的 API都是對(duì)文件系統(tǒng)的 wait-free 操作,不直接提供鎖這樣的分布式協(xié)同機(jī)制瘟斜。但是 data tree 的 API 非常強(qiáng)大缸夹,可以用來(lái)實(shí)現(xiàn)多種分布式協(xié)同機(jī)制。
znode 分類
一個(gè) znode 可以是持久性的螺句,也可以是臨時(shí)性的虽惭,znode 節(jié)點(diǎn)也可以是順序性的。每一個(gè)順序性的 znode 關(guān)聯(lián)一個(gè)唯一的單調(diào)遞增整數(shù)壹蔓,因此 ZooKeeper 主要有以下 4 種 znode:
- 持久性的 znode (PERSISTENT): ZooKeeper 宕機(jī)趟妥,或者 client 宕機(jī),這個(gè) znode 一旦創(chuàng)建就不會(huì)丟失佣蓉。
- 臨時(shí)性的 znode (EPHEMERAL): ZooKeeper 宕機(jī)了披摄,或者 client 在指定的 timeout 時(shí)間內(nèi)沒(méi)有連接 server,都會(huì)被認(rèn)為丟失勇凭。
- 持久順序性的 znode (PERSISTENT_SEQUENTIAL): znode 除了具備持久性 znode 的特點(diǎn)之外疚膊,znode 的名字具備順序性。
- 臨時(shí)順序性的 znode (EPHEMERAL_SEQUENTIAL): znode 除了具備臨時(shí)性 znode 的特點(diǎn)之外虾标,znode 的名字具備順序性寓盗。
安裝 ZooKeeper
到 https://archive.apache.org/dist/zookeeper/stable/ 下載 ZooKeeper,目前的最新版是 3.5.6璧函。
把 apache-zookeeper-3.5.6-bin.tar.gz 解壓到一個(gè)本地目錄 (目錄名最好不要包含空格和中文)傀蚌。我使用 /usr/local 目錄。
tar -zxvf apache-zookeeper-3.5.6-bin.tar.gz
把 conf 目錄下的 zoo_sample.cfg 重命名為 zoo.cfg蘸吓,然后修改配置善炫。
# 心跳檢查的時(shí)間 2秒
tickTime=2000
# 初始化時(shí) 連接到服務(wù)器端的間隔次數(shù),總時(shí)間10*2=20秒
initLimit=10
# ZK Leader 和follower 之間通訊的次數(shù)库继,總時(shí)間5*2=10秒
syncLimit=5
# 存儲(chǔ)內(nèi)存中數(shù)據(jù)快照的位置箩艺,如果不設(shè)置參數(shù)窜醉,更新事務(wù)日志將被存儲(chǔ)到默認(rèn)位置。
dataDir=/data/zookeeper
# ZK 服務(wù)器端的監(jiān)聽(tīng)端口
clientPort=2181
配置以下環(huán)境變量 vim /etc/profile
:
export ZOOKEEPER_HOME=/usr/local/apache-zookeeper-3.5.6-bin
export PATH=$PATH:$ZOOKEEPER_HOME/bin:$ZOOKEEPER_HOME/conf
啟動(dòng) Zookeeper
再安裝配置完成后艺谆,就可以啟動(dòng) Zookeeper榨惰,使用 zkServer.sh start 啟動(dòng) ZooKeeper 服務(wù):
[root@wupx apache-zookeeper-3.5.6-bin]# zkServer.sh start
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /usr/local/apache-zookeeper-3.5.6-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
檢查 ZooKeeper 日志是否有出錯(cuò)信息:
[root@wupx apache-zookeeper-3.5.6-bin]# cd logs/
[root@wupx logs]# grep -E -i "((exception)|(error))" *
因?yàn)榉祷貨](méi)有結(jié)果,說(shuō)明沒(méi)有錯(cuò)誤信息静汤。
檢查 ZooKeeper 數(shù)據(jù)文件琅催,這里存放的 ZooKeeper 的事務(wù)日志文件和快照日志文件。
[root@wupx zookeeper]# cd /data/zookeeper/
[root@wupx zookeeper]# tree
.
├── version-2
│ └── snapshot.0
└── zookeeper_server.pid
1 directory, 2 files
因?yàn)楝F(xiàn)在還沒(méi)有運(yùn)行任何 ZooKeeper 命令虫给,所以還沒(méi)有事務(wù)日志文件恢暖。
最后會(huì)檢查 ZooKeeper 是否在 2181 端口上監(jiān)聽(tīng)。
netstat -an | ag 2181
執(zhí)行后狰右,我們可以看到 ZooKeeper 已經(jīng)在 2181 這個(gè)端口上監(jiān)聽(tīng)了杰捂。
下面我們演示下如何使用 zkCli:
zkCli 使用
在執(zhí)行 zkCli.sh
命令后,會(huì)出現(xiàn)很多消息棋蚌,這些消息證明我們的 zkCli 和 ZooKeeper 的節(jié)點(diǎn)建立了有效連接嫁佳。
2019-12-22 10:38:36,684 [myid:localhost:2181] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@959] - Socket connection established, initiating session, client: /127.0.0.1:54038, server: localhost/127.0.0.1:2181
使用 ls -R /
可以遞歸查找 ZooKeeper 的 znode 節(jié)點(diǎn),使用 create /znode_name
可以創(chuàng)建 znode 節(jié)點(diǎn)谷暮,具體演示如下:
# 使用 ls -R 可以遞歸查找 ZooKeeper 的 znode 節(jié)點(diǎn)
[zk: localhost:2181(CONNECTED) 0] ls -R /
/
/zookeeper
/zookeeper/config
/zookeeper/quota
# 創(chuàng)建 znode /app1
[zk: localhost:2181(CONNECTED) 1] create /app1
Created /app1
[zk: localhost:2181(CONNECTED) 2] create /app2
Created /app2
[zk: localhost:2181(CONNECTED) 3] create /app1/p_1 1
Created /app1/p_1
[zk: localhost:2181(CONNECTED) 4] create /app1/p_2 2
Created /app1/p_2
[zk: localhost:2181(CONNECTED) 5] create /app1/p_3 3
Created /app1/p_3
[zk: localhost:2181(CONNECTED) 6] ls -R /
/
/app1
/app2
/zookeeper
/app1/p_1
/app1/p_2
/app1/p_3
/zookeeper/config
/zookeeper/quota
用 zkCli 實(shí)現(xiàn)鎖
分布式鎖要求如果鎖的持有者宕了蒿往,鎖可以被釋放。ZooKeeper 的 ephemeral 節(jié)點(diǎn)恰好具備這樣的特性湿弦。
接下來(lái)我們來(lái)演示下瓤漏,需要在兩個(gè)終端上分別啟動(dòng) zkCli,
在終端 1 上:
執(zhí)行 zkCli.sh
颊埃,再執(zhí)行 create -e /lock
命令蔬充,來(lái)建立臨時(shí) znode,加鎖的操作其實(shí)就是建立 znode 的過(guò)程班利,此時(shí)第一個(gè)客戶端加鎖成功饥漫。
接下來(lái)嘗試在第二個(gè)客戶端加鎖,在終端 2 上:
執(zhí)行 zkCli.sh
罗标,再執(zhí)行 create -e /lock
命令庸队,會(huì)發(fā)現(xiàn)提示 Node already exists: /lock
,提示 znode 已存在闯割,znode 建立失敗彻消,因此加鎖失敗,這時(shí)候我們來(lái)監(jiān)控這個(gè) znode宙拉,使用 stat -w /lock
來(lái)等待鎖被釋放宾尚。
這個(gè)時(shí)候我們退出第一個(gè)客戶端,在終端 1 上執(zhí)行 quit
命令鼓黔,會(huì)在客戶端 2 上收到一條 WATCHER 信息央勒,具體如下:
WATCHER::
WatchedEvent state:SyncConnected type:NodeDeleted path:/lock
再收到這個(gè)事件后再次在客戶端 2 上執(zhí)行加鎖,執(zhí)行 create -e /lock
澳化,會(huì)顯示創(chuàng)建 znode 成功崔步,即加鎖成功。
總結(jié)
這篇文章主要介紹了 ZooKeeper 的安裝配置缎谷,ZooKeeper 的基本概念和 zkCli 的使用井濒,并用 zkCli 來(lái)實(shí)現(xiàn)一個(gè)鎖,為后面更加深入的學(xué)習(xí)打好基礎(chǔ)列林。
參考
https://zookeeper.apache.org/doc/current/zookeeperOver.html
https://zookeeper.apache.org/doc/current/zookeeperStarted.html
《從Paxos到Zookeeper:分布式一致性原理與實(shí)踐》