Zookeeper
Zookeeper是一個分佈式數據一致性解決方案,致力於爲分佈式應用提供一個高性能,高可用,且具有嚴格順序訪問控制能力的分佈式協調存儲服務。
-
維護配置信息
在開發過程中碰見的配置項,地址,端口,name等,通常會將這些配置項放到配置文件中,再將配置文件放置到服務器上,需要更改配置時去服務器修改對應的配置文件,但是分佈式系統下,多臺服務器使用到配置文件,修改配置將是一件繁瑣的事情,人工去修改也容易出現錯誤,爲了保持各臺服務器上的配置數據一致,配置服務的高可用,所以需要高效且可靠的完成配置項的更新操作並保證配置數據的一致性。
zookeeper使用Zab這種一致性協議來保證一致性及zookeeper集羣來保證高可用性
dubbo框架中使用zookeeper管理一些配置來實現服務治理。
配置中心簡單實現可以參看我的博客:zookeeper-實現配置中心
-
分佈式鎖服務
zookeeper可以提供分佈式鎖。一個集羣是一個分佈式系統,由多臺服務器組成。爲了提高併發度和可靠性, 多臺服務器上運行着同一種服務。當多個服務在運行時就需要協調各服務的進度,有時候需要保證當某個服務在進行某個操作時,其他的服務都不能進行該操作,即對該操作 進行加鎖,如果當前機器掛掉後,釋放鎖其他的機器繼續執行該服務。
分佈式鎖的簡單實現可以參看我的博客:zookeeper-實現分佈式鎖
-
生成分佈式唯一ID
zookeeper在分佈式環境下生成全局唯一ID。做法如下:每次要生成一個新Id時,創建一個持久順序節點,創建 操作返回的節點序號,即爲新Id,然後把比自己節點小的刪除即可
生成分佈式唯一ID簡單實現可以參看我的博客:zookeeper-實現生成分佈式唯一id
-
集羣管理
一個集羣有時因爲各種軟硬件故障或者網絡故障,出現某些服務器掛掉而被移出集羣,而某些服務器加入到集羣中的情況,zookeeper會將這些服務器加入/移出的情況 通知給集羣中的其他正常工作的服務器,以及時調整存儲和計算等任務的分配和執行等。zookeeper通過選舉機制來確保集羣的可用性。此外zookeeper還會對故障的服務器做出診斷並嘗試修復。
集羣配置可以參看我的博客:zookeeper搭建
zookeeper數據模型
zookeeper的數據節點模型可以視爲樹狀結構
-
樹中的各個節點稱爲znode
-
節點的數據,zonde data(節點path,節點data)類似java map中的(kay,value)
-
每個znode下可以擁有子節點
-
每個節點都能設置相應的權限控制用戶的訪問
-
節點的屬性stat
cZxid:數據節點創建時的事務 ID
ctime:數據節點創建時的時間
mZxid:數據節點最後一次更新時的事務 ID
mtime:數據節點最後一次更新時的時間
pZxid:數據節點的子節點最後一次被修改時的事務 ID
cversion:子節點的更改次數
dataVersion:節點數據的更改次數
aclVersion:節點的 ACL 的更改次數
ephemeralOwner:如果節點是臨時節點,則表示創建該節點的會話的
SessionID;如果節點是持久節點,則該屬性值爲 0
dataLength:數據內容的長度
numChildren:數據節點當前的子節點個數
- 節點分爲永久節點和臨時節點
- 永久節點:該節點的生命週期不依賴於會話,並且只有在客戶端顯示執行刪除操作的時候,他們才能被刪除
- 臨時節點:該節點的生命週期依賴於創建它們的會話。一旦會話(Session)結束,臨時節點將被自動刪除,可以手動刪除。雖然每個臨時的Znode都會綁定到一個客戶端會話,但他們對所有的客戶端還是可見的。ZooKeeper的臨時節點不允許擁有子節點
Zookeeper常用指令
- 連接zookeeper
// 通過zkCli工具來連接zookeeper
./zkcli.sh
// 或者指定ip
./zkcli.sh -timeout 0 -r -server ip:port
- 創建節點
create [-s] [-e] path data #其中-s 爲有序節點,-e 臨時節點
例子:
//例子
//創建持久化節點
create /dubbo "123"
//創建持久化有序節點
create -s /dubbo "123"
//創建臨時節點,臨時節點會在會話過期後被刪除:
create -e /dubbo "123"
//創建臨時有序節點,臨時節點會在會話過期後被刪除:
create -s -e /dubbo "123"
- 更新節點
set path data
例子:
set /dubbo "123"
- 刪除
delete path [version]
例子
delete /dubbo
//也可以傳入版本號,當傳入的數據版本號和當前節點的數據版本號不符合的時候,zookeeper不會執行刪除操作
delete /dubbo 1
- 查看節點
get path
例子:
get /dubbo
//節點數據各個屬性:
cZxid:數據節點創建時的事務 ID
ctime:數據節點創建時的時間
mZxid:數據節點最後一次更新時的事務 ID
mtime:數據節點最後一次更新時的時間
pZxid:數據節點的子節點最後一次被修改時的事務 ID
cversion:子節點的更改次數
dataVersion:節點數據的更改次數
aclVersion:節點的 ACL 的更改次數
ephemeralOwner:如果節點是臨時節點,則表示創建該節點的會話的
SessionID;如果節點是持久節點,則該屬性值爲 0
dataLength:數據內容的長度
numChildren:數據節點當前的子節點個數
- 查看節點的狀態,返回值和get類似但是不會返回節點數據
stat /dubbo
- 查看節點列表
ls path
//不僅可以查看指定路徑的下的所有節點,還可以查看當前節點的信息
ls2 path
ZookeeperAPI實現
參看我的博客:ZookeeperAPI實現,節點的增刪改查
Zookeeper的會話管理
ZooKeeper 的每個客戶端都維護一組服務端信息,在創建連接時由應用指定,客戶端隨機選擇一個服務端進行連接,連接成功後,服務端爲每個連接分配一個唯一標識。
如果客戶端異常下線,或者網絡問題,導致一段時間沒心跳給 Zookeeper 服務端,則會被 Zookeeper 標記爲下線。
用戶客戶端和服務端之間的連接,可設置超時時間,通過心跳包機制(類似ping),檢查心跳結束,session就過期
session過期的時候,該session的所有臨時的節點都會被拋棄
Zookeeper的acl權限控制
zookeeper 類似文件系統,client 可以創建節點、更新節點、刪除節點,節點的權限的控制通過 acl(access control list)訪問控制列表來實現
acl的權限控制使用,使用scheme:id:permission
來標識
- 權限模式:scheme
方案 | 描述 |
---|---|
world | 只有一個用戶:anyone,代表登錄zookeeper所有人 |
ip | 對客戶端使用IP地址認證 |
auth | 使用已添加認證的用戶認證 |
digest | 使用“用戶名:密碼”方式認證 |
- 授權對象:id
授權對象ID是指,權限賦予的實體,例如:IP 地址或用戶。
- 權限:permission
create、delete、read、writer、admin也就是 增、刪、改、查、管理權限, 簡寫爲cdrwa
授權模式例子:
添加認證用戶:addauth digest <user>:<password>
- world授權模式:
setAcl <path> world:anyone:<acl>
create /node1 "node1"
setAcl /node1 world:anyone:cdrwa
getAcl /node1
- ip授權模式:
setAcl <path> ip:<ip>:<acl>
create /node1 "node1"
setAcl /node1 ip:192.168.21.141:cdrwa
getAcl /node1
- auth授權模式: setAcl
auth::
create /node1 "node1"
addauth digest user1:123456
setAcl /node1 auth:user1:cdrwa
getAcl /node1
-
digest授權模式 :
setAcl <path> digest:<user>:<password>:<acl>
密碼是經過SHA1以及Base64處理後的密文
create /node1 "node1"
setAcl /node1 digest:user1:密碼的密文:cdrwa
getAcl /node1
- 同一個節點可以多種模式授權
create /node1 "node1"
addauth digest user1:123456
setAcl /node1 ip:192.168.21.141:cdrwa,auth:user1:cdrwa,digest:cdrwa:密碼密文:cdrwa
zookeeper事件監聽機制
- watcher機制:
zookeeper提供了數據的發佈/訂閱功能,多個訂閱者可同時監聽某一特定主題對象,當該主題對象的自身狀態發生變化時(例如節點內容改變、節點下的子節點列表改變 等),會實時、主動通知所有訂閱者
zookeeper採用了Watcher機制實現數據的發佈/訂閱功能。該機制在被訂閱對象發生變化時會異步通知客戶端,因此客戶端不必在Watcher註冊後輪詢阻塞,從而減輕了客戶端壓力
-
watcher模型:
客戶端首先將watcher註冊到zk服務端,同時將watcher對象保存在客戶端的watcher管理器中,zk的服務端監聽到數據的變化後,通知客戶端,客戶端的watcher管理器觸發相關的watcher來回調來處理邏輯,完成整體的發佈/訂閱流程
-
watcher特性
-
一次性
watcher是一次性的,一旦被觸發就會移除,再次使用時需要重新註冊
-
輕量級
WatchEvent是最小的通信單元,結構上只包含通知狀態、事件類型和節點 路徑,並不會告訴數據節點變化前後的具體內容;
-
客戶端順序回調
watcher回調是順序串行化執行的,只有回調後客戶端才能看到最新的數 據狀態。一個watcher回調邏輯不應該太多,以免影響別的watcher執行
-
時效性
watcher只有在當前session徹底失效時纔會無效,若在session有效期內快速重連成功,則watcher依然存在,仍可接收到通知
-
-
watcher接口
Watcher是一個接口,任何實現了Watcher接口的類就是一個新的Watcher。
Watcher內部包含了兩個枚舉類:KeeperState、EventType
- Watcher通知狀態(KeeperState)
KeeperState是客戶端與服務端連接狀態發生變化時對應的通知類型
枚舉屬性 | 說明 |
---|---|
SyncConnected | 客戶端與服務器正常連接時 |
Disconnected | 客戶端與服務器斷開連接時 |
Expired | 會話session失效時 |
AuthFailed | 身份認證失敗時 |
- Watcher事件類型(EventType)
EventType是數據節點(znode)發生變化時對應的通知類型
EventType變化時,KeeperState永遠處於SyncConnected通知狀態下;當KeeperState發生變化時, EventType永遠爲None。
枚舉屬性 | 說明 |
---|---|
None | 無 |
NodeCreated | Watcher監聽的數據節點被創建時 |
NodeDeleted | Watcher監聽的數據節點被刪除時 |
NodeDataChanged | Watcher監聽的數據節點內容發生變更時(無論內容數據是否變化) |
NodeChildrenChanged | Watcher監聽的數據節點的子節點列表發生變更時 |
客戶端接收到的相關事件通知中只包含狀態及類型等信息,不包括節點變化前後的具體內容,變化前的數據需業務自身存儲,變化後的數據需調用get等方法重新獲取
- 捕獲相應的事件
在zookeeper中採用
zk.exists(path, watch),zk.getData(path, watcher, stat) ,zk.getChildren(path, watch)
這樣的方式爲某個znode註冊監聽
註冊方式 | created | childrenchanged | changed | delete |
---|---|---|---|---|
zk.exists(path, watch) | 可監控 | 可監控 | 可監控 | |
zk.getData(path, watcher, stat) | 可監控 | 可監控 | ||
zk.getChildren(path, watch) | 可監控 | 可監控 |
Zookeeper的工作原理
Zookeeper的核心是原子廣播,這個機制保證了各個server之間的同步,實現這個機制的協議是:一致性協議zab協議
zab協議 的全稱是 Zookeeper Atomic Broadcast (zookeeper原子廣播)。 zookeeper 是通過 zab協議來保證分佈式事務的最終一致性
基於zab協議,zookeeper集羣中的角色主要有以下三類:leader(領導者,可讀可寫),follower(跟隨着,只讀,有選舉權),observer(觀察者,只讀,無選舉權)
zab廣播模式工作原理,通過類似兩階段提交協議的方式解決數據一致性:
- leader從客戶端收到一個寫請求
- leader生成一個新的事務併爲這個事務生成一個唯一的ZXID
- leader將這個事務提議(propose)發送給所有的follows節點
- follower節點將收到的事務請求加入到歷史隊列(history queue)中,併發送ack給 leader
- 當leader收到大多數follower(半數以上節點)的ack消息,leader會發送commit請 求
- 當follower收到commit請求時,從歷史隊列中將事務請求commit
Zookeeper的同步流程
- Follower 連接 Leader ,將最大的 zxid 發送給 Leader 。
- Leader 根據 Follower 的 zxid 確定同步點。
- 完成同步後通知 Follower 已經成爲 update 狀態。
- Follower 收到 update 消息後,又可以重新接受 Client 的請求進行服務了。
Zookeeper的leader選舉
服務器狀態:
- looking:尋找leader狀態。當服務器處於該狀態時,它會認爲當前集羣中沒有 leader,因此需要進入leader選舉狀態。
- leading: 領導者狀態。表明當前服務器角色是leader。
- following: 跟隨者狀態。表明當前服務器角色是follower。
- observing:觀察者狀態。表明當前服務器角色是observer。
選舉分兩種情況:
-
服務器啓動時候的選舉:
在集羣初始化階段,當有一臺服務器server1啓動時,其單獨無法進行和完成 leader選舉,當第二臺服務器server2啓動時,此時兩臺機器可以相互通信,每臺機器都試圖找到leader,於是進入leader選舉過程。選舉過程如下:
- 每個server發出一個投票。由於是初始情況,server1和server2都會將自己作爲leader服務器來進行投票,每次投票會包含所推舉的服務器的myid和zxid,使用(myid, zxid)來表示,此時server1的投票爲(1, 0),server2的投票爲(2, 0),然後各自將這個投票發給集羣中其他機器。
- 集羣中的每臺服務器接收來自集羣中各個服務器的投票。
- 處理投票。針對每一個投票,服務器都需要將別人的投票和自己的投票進行pk,pk規則如下優先檢查zxid。zxid比較大的服務器優先作爲leader。 如果zxid相同,那麼就比較myid。myid較大的服務器作爲leader服務器。 對於Server1而言,它的投票是(1, 0),接收Server2的投票爲(2, 0),首先會比較 兩者的zxid,均爲0,再比較myid,此時server2的myid最大,於是更新自己的投票爲(2, 0),然後重新投票,對於server2而言,其無須更新自己的投票,只是再次向集羣中所有機器發出上一次投票信息即可
- 統計投票。每次投票後,服務器都會統計投票信息,判斷是否已經有過半機器接受到 相同的投票信息對於server1、server2而言,都統計出集羣中已經有兩臺機器接受了(2, 0)的投票信息,此時便認爲已經選出了leader
- 改變服務器狀態。一旦確定了leader,每個服務器就會更新自己的狀態,如果是follower,那麼就變更爲following,如果是leader,就變更爲leading。
-
服務器運行時期的選舉:
在zookeeper運行期間,leader與非leader服務器各司其職,即便當有非leader服務器宕機或新加入,此時也不會影響leader,但是一旦leader服務器掛了,那麼整個集羣將暫停對外服務,進入新一輪leader選舉,其過程和啓動時期的Leader選舉過程基本一致
-
變更狀態。leader掛後,餘下的服務器都會將自己的服務器狀態變更爲looking,然後開始進入leader選舉過程。
-
每個server會發出一個投票。在運行期間,每個服務器上的zxid可能不同,那麼剩下的機器中zxid大的被選舉爲leader,
-
如果此時剩下的節點的zxid都一樣,則比較myid,myid大的被選舉爲leader
-
改變服務器的狀態。與啓動時過程相同。
-
Zookeeper集羣節點數爲什麼是單數
在統計投票時,有個過半的概念,大於集羣機器數量的一半,即大於或等於(n/2+1
)。
集羣數量 | 至少正常運行數量 | 允許掛掉的數量 |
---|---|---|
2 | 2 的半數爲 1,半數以上最少爲 2 | 0 |
3 | 3 的半數爲 1.5,半數以上最少爲 2 | 1 |
4 | 4 的半數爲 2,半數以上最少爲 3 | 1 |
5 | 5 的半數爲 2.5,半數以上最少爲 3 | 2 |
6 | 6 的半數爲 3,半數以上最少爲 4 | 2 |
通過表格可以直觀的發現,當節點數是3臺和4臺時候,允許掛掉的節點數都是1,節點數是5臺和6臺的時候,允許掛掉的節點數都爲2,這是由於半數以上投票通過決定的
從服務器的資源角度來看就是單數節點省錢
Zookeeper的部署方式
- 單機
- 集羣
可以參看我的博客:zookeeper搭建