天天看點

不會這些概念,勸你履歷不要寫 “熟悉” zookeeper

唠唠叨叨

本文主要分享一下

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);           
  • PERSISTENT

    -持久化目錄節點 :client建立節點後,與zookeeper斷開連接配接該節點将被持久化,當client再次連接配接後節點依舊存在。
  • PERSISTENT_SEQUENTIAL

    -持久化順序節點 :client建立節點後,與zookeeper斷開連接配接該節點将被持久化,再次連接配接節點還存在,zookeeper會給該節點名稱進行順序編号,例如:/lock/0000000001、/lock/0000000002、/lock/0000000003。
    • EPHEMERAL

      -臨時目錄節點 : client與zookeeper斷開連接配接後,該節點即會被删除
    • EPHEMERAL_SEQUENTIAL

      -臨時順序節點 : client與zookeeper斷開連接配接後,該節點被删除,會給該節點名稱進行順序編号,例如:/lock/0000000001、/lock/0000000002、/lock/0000000003。

二、節點的ACL權限控制

ACL

:即

Access Control List

(節點的權限控制),通過

ACL

機制來解決

znode

節點的通路權限問題,要注意的是

zookeeper

對權限的控制是基于

znode

級别的,也就說節點之間的權限不具有繼承性,即子節點不繼承父節點的權限。

zookeeper

中設定ACL權限的格式由

<schema>:<id>:<acl>

三段組成。

schema :表示授權的方式

  • world

    :表示任何人都可以通路
  • auth

    :隻有認證的使用者可以通路
  • digest

    :使用username :password使用者密碼生成MD5哈希值作為認證ID
  • host/ip

    :使用用戶端主機IP位址來進行認證

id: 權限的作用域,用來辨別身份,依賴于schema選擇哪種方式。

acl:給一個節點賦予哪些權限,節點的權限有create,、delete、write、read、admin 統稱

cdwra

1、

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

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

:使用者名:密碼的驗證方式

使用者名:密碼方式授權是針對單個特定使用者,這種方式是不需要先添加認證使用者的。

如果在代碼中使用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

這種方式就比較好了解了,通過對特定的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服務端

    注冊服務資訊,也就是建立一個節點,例如:使用者注冊服務

    com.xxx.user.register

    ,并在節點上存儲服務的相關資料(如服務提供者的ip位址、端口等)。
  • 服務發現: 服務消費者(

    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叢集我們下期見喽。