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