你的簡歷寫了 “熟悉” zookeeper ?那這些你會嗎?
本文主要分享一下zookeeper
的一些基本概念,在正式進入正題前,和大家聊一聊剛入行時我的面試經(jīng)驗,可以說是耿直的有些可愛。
面試官:用過zookeeper
嗎?
我:用過啊,給dubbo
提供服務的注冊與發(fā)現(xiàn)嘛
面試官:知道 zookeeper
是什么嗎?
我:知道啊,注冊中心嘛
面試官:那你們項目中都是怎么用 zookeeper
的?
我:就在 springboot
的 application.properties
配置文件里添加一個 zookeeper
服務地址就行了。。。
上邊的對話好像也沒什么毛病,但似乎又感覺哪里有點不太對,結果就是每次我如此回答面試都被pass。
為什么會被問zookeeper?因為我的簡歷項目上寫著熟練使用zookeeper,可面試官理解的 “熟練” 使用可不是會配置,工程啟動不報錯那么簡單。所以還是有必要全面了解一下zookeeper的相關知識。
一、zookeeper初識?
Zookeeper
它作為Hadoop
項目中的一個開源子項目,是一個經(jīng)典的分布式數(shù)據(jù)一致性解決方案,致力于為分布式應用提供一個高性能、高可用,且具有嚴格順序訪問控制能力的分布式協(xié)調服務。
1、zookeeper數(shù)據(jù)模型
zookeeper
維護了一個類似文件系統(tǒng)的數(shù)據(jù)結構,每個子目錄(/微信、/微信/公眾號)都被稱作為 znode
即節(jié)點。和文件系統(tǒng)一樣,我們可以很輕松的對 znode
節(jié)點進行增加、刪除等操作,而且還可以在一個znode
下增加、刪除子znode
,區(qū)別在于文件系統(tǒng)的是,znode
可以存儲數(shù)據(jù)(嚴格說是必須存放數(shù)據(jù),默認是個空字符)。
由于zookeeper
是目錄節(jié)點結構,在獲取和創(chuàng)建節(jié)點時,必須要以“/”
開頭,否則在獲取節(jié)點時會報錯 Path must start with / character
。
1[zk: localhost:2181(CONNECTED) 13] get test
2Command failed: java.lang.IllegalArgumentException: Path must start with / character
根節(jié)點名必須為“/XXX”
,創(chuàng)建子節(jié)點時必須要帶上根節(jié)點目錄“/XXX/CCC”
、“/XXX/AAA”
。
例如:想要獲取下圖 程序員內點事
節(jié)點必須拼接完整的路徑 get /微信/公眾號/程序員內點事
1get /微信/公眾號/程序員內點事
2、znode節(jié)點屬性
一個znode
節(jié)點不僅可以存儲數(shù)據(jù),還有一些其他特別的屬性。接下來我們創(chuàng)建一個/test
節(jié)點分析一下它各個屬性的含義。
1[zk: localhost:2181(CONNECTED) 6] get /test
2456
3cZxid = 0x59ac //
4ctime = Mon Mar 30 15:20:08 CST 2020
5mZxid = 0x59ad
6mtime = Mon Mar 30 15:22:25 CST 2020
7pZxid = 0x59ac
8cversion = 0
9dataVersion = 2
10aclVersion = 0
11ephemeralOwner = 0x0
12dataLength = 3
13numChildren = 0
我們看到一個znode
節(jié)點的屬性比較多,但比較主要的屬性還是zxid
、version
、acl
這三個。
Zxid:
znode
節(jié)點狀態(tài)改變會導致該節(jié)點收到一個zxid
格式的時間戳,這個時間戳是全局有序的,znode節(jié)點的建立或者更新都會產(chǎn)生一個新的。如果zxid1
的值 < zxid2
的值,那么說明zxid2
發(fā)生的改變在zxid1
之后。每個znode節(jié)點都有3個zxid
屬性,cZxid
(節(jié)點創(chuàng)建時間)、mZxid
(該節(jié)點修改時間,與子節(jié)點無關)、pZxid
(該節(jié)點或者該節(jié)點的子節(jié)點的最后一次創(chuàng)建或者修改時間,孫子節(jié)點無關)。
zxid
屬性主要應用于zookeeper
的集群,這個后邊介紹集群時詳細說。
Version:
znode
屬性中一共有三個版本號dataversion
(數(shù)據(jù)版本號)、cversion
(子節(jié)點版本號)、aclversion
(節(jié)點所擁有的ACL權限版本號)。
znode
中的數(shù)據(jù)可以有多個版本,如果某一個節(jié)點下存有多個數(shù)據(jù)版本,那么查詢這個節(jié)點數(shù)據(jù)就需要帶上版本號。每當我們對znode
節(jié)點數(shù)據(jù)修改后,該節(jié)點的dataversion
版本號會遞增。當客戶端請求該znode
節(jié)點時,會同時返回節(jié)點數(shù)據(jù)和版本號。另外當dataversion
為 -1
的時候可以忽略版本進行操作。對一個節(jié)點設置權限時aclVersion
版本號會遞增,下邊會詳細說ACL權限控制。
驗證一下,我們修改/test
節(jié)點的數(shù)據(jù)看看dataVersion
有什么變化,發(fā)現(xiàn)dataVersion
屬性變成了 3,版本號遞增了。
1[zk: localhost:2181(CONNECTED) 10] set /test 8888
2cZxid = 0x59ac
3ctime = Mon Mar 30 15:20:08 CST 2020
4mZxid = 0x59b6
5mtime = Mon Mar 30 16:58:08 CST 2020
6pZxid = 0x59ac
7cversion = 0
8dataVersion = 3
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 4
12numChildren = 0
3、znode的類型
zookeeper
有四種類型的znode
,在用客戶端 client
創(chuàng)建節(jié)點的時候需要指定類型。
1zookeeper.create("/公眾號/程序員內點事", "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
PERSISTENT
-持久化目錄節(jié)點 :client創(chuàng)建節(jié)點后,與zookeeper斷開連接該節(jié)點將被持久化,當client再次連接后節(jié)點依舊存在。PERSISTENT_SEQUENTIAL
-持久化順序節(jié)點 :client創(chuàng)建節(jié)點后,與zookeeper斷開連接該節(jié)點將被持久化,再次連接節(jié)點還存在,zookeeper會給該節(jié)點名稱進行順序編號,例如:/lock/0000000001、/lock/0000000002、/lock/0000000003。EPHEMERAL
-臨時目錄節(jié)點 :client與zookeeper斷開連接后,該節(jié)點即會被刪除EPHEMERAL_SEQUENTIAL
-臨時順序節(jié)點 :client與zookeeper斷開連接后,該節(jié)點被刪除,會給該節(jié)點名稱進行順序編號,例如:/lock/0000000001、/lock/0000000002、/lock/0000000003。
二、節(jié)點的ACL權限控制
ACL
:即 Access Control List
(節(jié)點的權限控制),通過ACL
機制來解決znode
節(jié)點的訪問權限問題,要注意的是zookeeper
對權限的控制是基于znode
級別的,也就說節(jié)點之間的權限不具有繼承性,即子節(jié)點不繼承父節(jié)點的權限。
zookeeper
中設置ACL權限的格式由<schema>:<id>:<acl>
三段組成。
schema :表示授權的方式
world
:表示任何人都可以訪問auth
:只有認證的用戶可以訪問digest
:使用username :password用戶密碼生成MD5哈希值作為認證IDhost/ip
:使用客戶端主機IP地址來進行認證
id:權限的作用域,用來標識身份,依賴于schema選擇哪種方式。
acl:給一個節(jié)點賦予哪些權限,節(jié)點的權限有create,、delete、write、read、admin 統(tǒng)稱 cdwra
。
1、world:表示任何人都可以訪問
我們用 getAcl
命令來看一下,沒有設置過權限的znode
節(jié)點,默認情況下的權限情況。
1[zk: localhost:2181(CONNECTED) 12] getAcl /test
2'world,'anyone
3: cdrwa
看到?jīng)]有設置ACL屬性的節(jié)點,默認schema 使用的是world
,作用域是anyone
,節(jié)點權限是cdwra
,也就是說任何人都可以訪問。
那我們如果要給一個schema 為非world
的節(jié)點設置world
權限咋搞?
1setAcl /test world:anyone:crdwa
2、auth:只有認證的用戶可以訪問
schema 用auth
授權表示只有認證后的用戶才可以訪問,那么首先就需要添加認證用戶,添加完以后需要對認證的用戶設置ACL權限。
1addauth digest test:password(明文)
需要注意的是設置認證用戶時的密碼是明文的。
1[zk: localhost:2181(CONNECTED) 2] addauth digest user:user //用戶名:密碼
2[zk: localhost:2181(CONNECTED) 5] setAcl /test auth:user:crdwa
3[zk: localhost:2181(CONNECTED) 6] getAcl /test
4'digest,'user:ben+k/3JomjGj4mfd4fYsfM6p0A=
5: cdrwa
實際上我們這樣設置以后,就是將這個節(jié)點開放給所有認證的用戶,setAcl /test auth:user:crdwa
相當于setAcl /test auth::crdwa
。
3、digest:用戶名:密碼的驗證方式
用戶名:密碼方式授權是針對單個特定用戶,這種方式是不需要先添加認證用戶的。
如果在代碼中使用zookeeper客戶端設置ACL,那么密碼是明文的,但若是zk.cli等客戶端操作就需要將密碼進行sha1
及base64
處理。
1setAcl <path> digest:<user>:<password(密文)>:<acl>
2
3setAcl /test digest:user:jalRr+knv/6L2uXdenC93dEDNuE=:crdwa
那么密碼如何加密嘞?有以下幾種方式:
通過shell
命令加密
1echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64
2
使用zookeeper
自帶的類庫org.apache.zookeeper.server.auth.DigestAuthenticationProvider
生成
1java -cp /zookeeper-3.4.13/zookeeper-3.4.13.jar:/zookeeper-3.4.13/lib/slf4j-api-1.7.25.jar \
2 org.apache.zookeeper.server.auth.DigestAuthenticationProvider \
3 root:root
4root:root->root:qiTlqPLK7XM2ht3HMn02qRpkKIE=
4、host/ip:使用客戶端主機IP地址來進行認證
這種方式就比較好理解了,通過對特定的IP地址,也可以是一個IP段進行授權。
1[zk: localhost:2181(CONNECTED) 3] setAcl /test0000000014 ip:127.0.0.1:crdwa
2cZxid = 0x59ac
3ctime = Mon Mar 30 15:20:08 CST 2020
4mZxid = 0x59b6
5mtime = Mon Mar 30 16:58:08 CST 2020
6pZxid = 0x59ac
7cversion = 0
8dataVersion = 3
9aclVersion = 3 // 這個版本一直在增加
10ephemeralOwner = 0x0
11dataLength = 4
12numChildren = 0
三、zookeeper的靈魂 watcher
我們在開頭就說過:zookeeper
可以為dubbo
提供服務的注冊與發(fā)現(xiàn),作為注冊中心,但你有想過zookeeper
為啥能夠實現(xiàn)服務的注冊與發(fā)現(xiàn)嗎?這就不得不說一下zookeeper
的靈魂 Watcher
(監(jiān)聽者)。
1、watcher是個啥?
watcher
是zooKeeper
中一個非常核心功能 ,客戶端watcher
可以監(jiān)控節(jié)點的數(shù)據(jù)變化以及它子節(jié)點的變化,一旦這些狀態(tài)發(fā)生變化,zooKeeper服務端就會通知所有在這個節(jié)點上設置過watcher
的客戶端 ,從而每個客戶端都很快感知,它所監(jiān)聽的節(jié)點狀態(tài)發(fā)生變化,而做出對應的邏輯處理。
簡單的介紹了一下watcher
,那么我們來分析一下,zookeeper
是如何實現(xiàn)服務的注冊與發(fā)現(xiàn)。zookeeper
的服務注冊與發(fā)現(xiàn),主要應用的是zookeeper
的znode
節(jié)點數(shù)據(jù)模型和watcher
機制,大致的流程如下:
服務注冊: 服務提供者(
Provider
)啟動時,會向zookeeper服務端
注冊服務信息,也就是創(chuàng)建一個節(jié)點,例如:用戶注冊服務com.xxx.user.register
,并在節(jié)點上存儲服務的相關數(shù)據(jù)(如服務提供者的ip地址、端口等)。服務發(fā)現(xiàn): 服務消費者(
Consumer
)啟動時,根據(jù)自身配置的依賴服務信息,向zookeeper服務端
獲取注冊的服務信息并設置watch監(jiān)聽
,獲取到注冊的服務信息之后,將服務提供者的信息緩存在本地,并進行服務的調用。服務通知: 一旦服務提供者因某種原因宕機不再提供服務之后,客戶端與
zookeeper
服務端斷開連接,zookeeper
服務端上服務提供者對應服務節(jié)點會被刪除(例如:用戶注冊服務com.xxx.user.register
),隨后zookeeper
服務端會異步向所有消費用戶注冊服務com.xxx.user.register
,且設置了watch監(jiān)聽
的服務消費者發(fā)出節(jié)點被刪除的通知,消費者根據(jù)收到的通知拉取最新服務列表,更新本地緩存的服務列表。
上邊的過程就是zookeeper
可以實現(xiàn)服務注冊與發(fā)現(xiàn)的大致原理。
2、watcher類型
znode
節(jié)點可以設置兩類watch
,一種是DataWatches
,基于znode節(jié)點的數(shù)據(jù)變更從而觸發(fā) watch
事件,觸發(fā)條件getData()
、exists()
、setData()
、 create()
。
另一種是Child Watches
,基于znode的孩子節(jié)點發(fā)生變更觸發(fā)的watch事件,觸發(fā)條件 getChildren()
、 create()
。
而在調用 delete()
方法刪除znode時,則會同時觸發(fā)Data Watches
和Child Watches
,如果被刪除的節(jié)點還有父節(jié)點,則父節(jié)點會觸發(fā)一個Child Watches
。
3、watcher特性
watch
對節(jié)點的監(jiān)聽事件是一次性的!客戶端在指定的節(jié)點設置了監(jiān)聽watch
,一旦該節(jié)點數(shù)據(jù)發(fā)生變更通知一次客戶端后,客戶端對該節(jié)點的監(jiān)聽事件就失效了。
如果還要繼續(xù)監(jiān)聽這個節(jié)點,就需要我們在客戶端的監(jiān)聽回調中,再次對節(jié)點的監(jiān)聽watch
事件設置為True
。否則客戶端只能接收到一次該節(jié)點的變更通知。
四、zookeeper能實現(xiàn)哪些功能
服務的注冊與發(fā)現(xiàn)功能只是zookeeper的冰山一角,它還能實現(xiàn)諸如分布式鎖、隊列、配置中心等一系列功能,接下來我們只分析一下原理,具體的實現(xiàn)大家上網(wǎng)查一下資料還是比較全的。
1、分布式鎖
zookeeper
基于watcher
機制和znode
的有序節(jié)點,天生就是一個分布式鎖的坯子。首先創(chuàng)建一個/test/lock
父節(jié)點作為一把鎖,盡量是持久節(jié)點(PERSISTENT類型),每個嘗試獲取這把鎖的客戶端,在/test/lock
父節(jié)點下創(chuàng)建臨時順序子節(jié)點。
由于序號的遞增性,我們規(guī)定序號最小的節(jié)點即獲得鎖。例如:客戶端來獲取鎖,在/test/lock
節(jié)點下創(chuàng)建節(jié)點為/test/lock/seq-00000001
,它是最小的所以它優(yōu)先拿到了鎖,其它節(jié)點等待通知再次獲取鎖。/test/lock/seq-00000001
執(zhí)行完自己的邏輯后刪除節(jié)點釋放鎖。
那么節(jié)點/test/lock/seq-00000002
想要獲取鎖等誰的通知呢?
這里我們讓/test/lock/seq-00000002
節(jié)點監(jiān)聽/test/lock/seq-00000001
節(jié)點,一旦/test/lock/seq-00000001
節(jié)點刪除,則通知/test/lock/seq-00000002
節(jié)點,讓它再次判斷自己是不是最小的節(jié)點,是則拿到鎖,不是繼續(xù)等通知。
以此類推/test/lock/seq-00000003
節(jié)點監(jiān)聽/test/lock/seq-00000002
節(jié)點,總是讓后一個節(jié)點監(jiān)聽前一個節(jié)點,不用讓所有節(jié)點都監(jiān)聽最小的節(jié)點,避免設置不必要的監(jiān)聽,以免造成大量無效的通知,形成“羊群效應”。
zookeeper
分布式鎖和redis
分布式鎖相比,因為大量的創(chuàng)建、刪除節(jié)點性能上比較差,并不是很推薦。
2、分布式隊列
zookeeper實現(xiàn)分布式隊列也很簡單,應用znode的有序節(jié)點天然的“先進先出”,后創(chuàng)建的節(jié)點總是最大的,出隊總是拿序號最小的節(jié)點即可。
3、配置管理
現(xiàn)在有很多開源項目都在使用Zookeeper來維護配置,像消息隊列Kafka中,就使用Zookeeper來維護broker的信息;dubbo中管理服務的配置信息。原理也是基于watcher
機制,例如:創(chuàng)建一個/config
節(jié)點存放一些配置,客戶端監(jiān)聽這個節(jié)點,一點修改/config
節(jié)點的配置信息,通知各個客戶端數(shù)據(jù)變更重新拉取配置信息。
4、命名服務
zookeeper
的命名服務:也就是我們常說的服務注冊與發(fā)現(xiàn),主要是根據(jù)指定名字來獲取資源或服務的地址,服務提供者等信息,利用其znode
節(jié)點的特點和watcher
機制,將其作為動態(tài)注冊和獲取服務信息的配置中心,統(tǒng)一管理服務名稱和其對應的服務器列表信息,我們能夠近乎實時地感知到后端服務器的狀態(tài)(上線、下線、宕機)。
本文旨在給大家介紹一下zookeeper的基礎知識,像面試中被問頻率較高的zookeeper集群選主等概念,并沒有放在這期來寫,因為集群的內容也是比較多的,我怕篇幅太長大家沒有耐心看完(其實就是有點犯懶了,哈哈哈?。?/span>
感興趣的小伙伴可以關注一波。
特別推薦一個分享架構+算法的優(yōu)質內容,還沒關注的小伙伴,可以長按關注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!