Zookeeper--簡介

在這裏插入圖片描述

前言–分佈式協調技術

    其實分佈式協調技術主要是用來解決分佈式環境下的多個進程(程序)之間的同步控制,讓他們有序的訪問某種臨界資源,防止造成“髒數據”。對於分佈式系統而言,它並不像我們平時的進程(程序)都是在一臺計算機上運行的,那樣確實好辦,寫調度算法就可以解決了,問題就在於它是一個分佈式的環境下的,這裏涉及到“部分丟失”的概念,
    分析分佈式系統中如何進行進程的調度,假設一個機器上掛載了資源,有多個物理分佈的進程都要競爭這個資源,但我們有不希望他們是同時進行訪問的,因此,就需要一個協調器來解決,這個協調器就是我們在java中經常用到的“鎖”,因爲是在分佈式系統環境下,也稱爲分佈式鎖。當某個進程獲得鎖後,就保持了對該資源的獨佔,其他的進程就無法訪問此資源,而處於等待的狀態,直到此進程釋放鎖,讓其他進程獲取。分佈式鎖也是分佈式協調技術要實現的核心內容。下面進行詳細的講解。

分佈式鎖的實現

    我們可能的錯覺就是,分佈式環境下進行分佈式鎖,無非就是將原來在同一臺機器上對進程的調度的原語,通過網絡環境實現在分佈式環境中。問題就在於分佈式環境中,所有在同一臺機器上的假設都不存在,因爲:網絡是不可靠的。

    比如:在同一臺機器上,你對一個服務的調用如果成功,那就是成功,如果調用失敗,比如拋出異常那就是調用失敗。但是在分佈式環境中,由於網絡的不可靠,你對一個服務的調用失敗了並不表示一定是失敗的,可能是執行成功了,但是響應返回的時候失敗了。還有,A和B都去調用C服務,在時間上 A還先調用一些,B後調用,那麼最後的結果是不是一定A的請求就先於B到達呢? 這些在同一臺機器上的種種假設,我們都要重新思考,我們還要思考這些問題給我們的設計和編碼帶來了哪些影響。還有,在分佈式環境中爲了提升可靠性,我們往往會部署多套服務,但是如何在多套服務中達到一致性,這在同一臺機器上多個進程之間的同步相對來說比較容易辦到,但在分佈式環境中確實一個大難題。

    所以分佈式協調遠比在同一臺機器上對多個進程的調度要難得多,而且如果爲每一個分佈式應用都開發一個獨立的協調程序。一方面,協調程序的反覆編寫浪費,且難以形成通用、伸縮性好的協調器。另一方面,協調程序開銷比較大,會影響系統原有的性能。所以,急需一種高可靠、高可用的通用協調機制來用以協調分佈式應用。

    當下,在分佈式協調技術方面做得比較好的就是Google的Chubby和Apache的Zookeeper了,他們作爲分佈式鎖的實現者。

    因爲Chubby是作爲Google的自身項目,用於商業環境,而Zookeeper則是由雅虎模仿Google的Chubby開發出來的開源的項目。而且在分佈式領域內,Zookeeper久經考驗,它的可靠性,高可用性都是經過理論和時間的驗證的,所以我們在構建一些分佈式系統的時候,可以以這些系統爲起點構建我們的系統,這樣會節省不少成本,而且bug也會更少。

Zookeeper概述

    ZooKeeper是一種爲分佈式應用所設計的高可用、高性能且一致的開源協調服務,它提供了一項基本服務:分佈式鎖服務。由於ZooKeeper的開源特性,後來我們的開發者在分佈式鎖的基礎上,摸索了出了其他的使用方法:配置維護、組服務、分佈式消息隊列、分佈式通知/協調等。
    Zookeeper主要是針對大型分佈式系統進行高可靠的協調。由這個定義我們知道zookeeper是個協調系統,作用的對象是分佈式系統。說到協調,
    以網上常用的一個例子:在實際應用中,我們可以聯想到的現實生活中很多十字路口的交通協管,他們手握着小紅旗,指揮車輛和行人是不是可以通行。如果我們把車輛和行人比喻成運行在計算機中的單元(線程),那麼這個協管是幹什麼的?很多人都會想到,這不就是鎖麼?對,在一個併發的環境裏,我們爲了避免多個運行單元對共享數據同時進行修改,造成數據損壞的情況出現,我們就必須依賴像鎖這樣的協調機制,讓有的線程可以先操作這些資源,然後其他線程等待。

ZooKeeper特性

讀、寫(更新)模式

    在ZooKeeper集羣中,讀可以從任意一個ZooKeeper Server讀,這一點是保證ZooKeeper比較好的讀性能的關鍵;寫的請求會先Forwarder到Leader,然後由Leader來通過ZooKeeper中的原子廣播協議(ZAB,Zookeeper Atomic Broadcast),將請求廣播給所有的Follower,Leader收到一半以上的寫成功的Ack後,就認爲該寫成功了,就會將該寫進行持久化,並告訴客戶端寫成功了。

在這裏插入圖片描述

WAL和Snapshot

    和大多數分佈式系統一樣,ZooKeeper也有WAL(Write-Ahead-Log),對於每一個更新操作,ZooKeeper都會先寫WAL, 然後再對內存中的數據做更新,然後向Client通知更新結果。另外,ZooKeeper還會定期將內存中的目錄樹進行Snapshot,落地到磁盤上,這個跟HDFS中的FSImage是比較類似的。這麼做的主要目的,一當然是數據的持久化,二是加快重啓之後的恢復速度,如果全部通過Replay WAL的形式恢復的話,會比較慢。
FIFO
    對於每一個ZooKeeper客戶端而言,所有的操作都是遵循FIFO順序的,這一特性是由下面兩個基本特性來保證的:一是ZooKeeper Client與Server之間的網絡通信是基於TCP,TCP保證了Client/Server之間傳輸包的順序;二是ZooKeeper Server執行客戶端請求也是嚴格按照FIFO順序的。

注意:
ZooKeeper性能上的特點決定了它能夠用在大型的、分佈式的系統當中。從可靠性方面來說,它並不會因爲一個節點的錯誤而崩潰(因爲它在一定程度上就是爲了解決單點故障的問題,不能只是將問題進行轉移)。除此之外,它嚴格的序列訪問控制意味着複雜的控制原語可以應用在客戶端上。ZooKeeper在一致性、可用性、容錯性的保證,也是ZooKeeper的成功之處,它獲得的一切成功都與它採用的協議——Zab協議是密不可分的,這些內容將會在後面介紹。

運行模式

Zookeeper服務有兩種不同的運行模式:

  • 獨立模式(standalone mode):即只有一個Zookeeper服務器,這種一般適合於測試環境,但不能保證高可用和恢復性
  • 複製模式(replication mode):是在生產環境下使用,運行與一個計算機集羣上,這個計算機集羣稱爲一個“集合體"(ensemble)

在這裏插入圖片描述
    ZooKeeper通過複製來實現高可用性,只要集合體中半數以上的機器處於可用狀態,它就能夠提供服務。例如,在一個有5個節點的集合體中,每個Follower節點的數據都是Leader節點數據的副本,也就是說我們的每個節點的數據視圖都是一樣的,這樣就可以有五個節點提供ZooKeeper服務。並且集合體中任意2臺機器出現故障,都可以保證服務繼續,因爲剩下的3臺機器超過了半數。
    從概念上來講,Zookeeper所做的就是確保對Znode的每個修改都會被複制到集合體中超過半數的機器上,如果少於半數的機器出現故障,則最少有一臺機器會保存最新的狀態,那麼這臺機器就是Leader,其餘複本最終也會更新到這個狀態,而如果Leader掛了,由於其他機器保存了Leader的複本,那就從中選出一臺機器作爲新的Leader繼續提供服務。

Zookeeper數據模型

系統結構

Zookeeper會維護一個具有層次關係的數據結構,它又非常類似於linux下的文件系統。如下圖:
在這裏插入圖片描述
他們都採用的是含有節點的目錄樹結構,在Zookeeper中的每個節點稱爲:Znode。這裏有了自身的定義,那麼它便和文件系統的目錄樹有一些不一樣的地方:

  1. 引用方式
    Znode通過路徑引用,且路徑必須是絕對路徑,因此每個路徑必須由“/”開頭;並且,他們是唯一的,每個路徑只有一個表示,Zookeeper中的路徑是由Unicode字符串組成的,其中“/zookeeper”用來保存管理信息,作爲系統的配置。
  2. Znode結構
    Zookeeper命名空間中的Znode,同時具有文件和目錄兩種特點,即:既能像文件一樣維護數據,元信息,ACL,時間戳等數據結構,又能像目錄一樣作爲路徑表示的一部分。上圖中的每個節點有3個部分組成:
    • stat:狀態信息,描述該Znode的版本,權限等信息
    • data:數據信息,即該Znode保存的數據
    • children:該Znode下的子節點信息

注意:
雖然Zookeeper可以關聯一些數據,但並沒有被設計作爲常規的數據庫或大數據存儲,而是作爲管理調度數據的服務,比如分佈式應用中的配置文件信息,狀態信息,彙集位置等。這些數據的共同特性就是他們都是很小的數據,通常都是以KB爲單位。
Zookeeper的服務器和客戶端都會嚴格檢查並限制每個Znode的數據大小最大爲1M,但常規的文件都應該遠小於此值。

狀態信息

主要記錄的是節點狀態改變的時間戳,所以很多都是記錄時間的形式在作爲狀態信息的。

Zxid

使節點狀態改變的每個操作都會讓節點收到一個Zxid格式的時間戳,並且這個時間戳全局有序,也就是每個對節點的改變都將產生唯一的Zxid(這也與鎖的機制相關)。如果Zxid1的值小於Zxid2的值,那麼Zxid1所對應的事件發生在Zxid所對應的時間之前。而Zookeeper在每個節點會維護三個Zxid值:

  • cZxid:節點的創建時間所對應的Zxid格式時間戳
  • mZxid:節點的修改時間所對應的Zxid格式時間戳
  • pZxid:節點最新修改的時間所對應的Zxid格式的時間戳
版本號(version)

對於節點來說,每個操作都會使這個節點的版本號增加,每個節點維護三個版本號:

  • version:節點數據版本號,也稱爲 dataversion
  • cverison:子節點版本號
  • aversion:節點擁有的ACL版本號
數據訪問

Zookeeper中的每個接單存儲的數據要被原子性操作,也就是讀操作將獲取與節點相關的所有數據,寫操作也將替換掉節點的所有數據,並且,每個節點都擁有自己的ACL(訪問控制列表),這個列表中規定了用戶的權限。

  1. 權限管理
    每個節點都可以管理自身的ACL列表,控制自身的訪問權限,需要注意的是,exists和getAcl操作是不受權限的限制的,每個客戶端都是可以調用這兩個方法的。
  • ACL包括:
    • 權限:perms
    • 驗證模式:schema
    • 具體內容:Ids
  • 驗證模式包括:
    • Digest:客戶端需要通過用戶名和密碼驗證,如user:pwd形式
    • Host:有客戶端主機名驗證
    • Ip:有ip地址驗證
    • World:固定用戶爲所有人,anyone
  • 管理的權限包括:
    - admin(setacl()),delete,create,write,read

那麼設置的一般就是驗證的方式schema,然後就是可以操作的權限,驗證的方式一般選用digest方式是最常用和方便的,對於操作權限的設置,則是可以通過int型數字和二進制表示,這個有點像文件系統的的rwx,只是這裏是adcwr,

  • 使用digest方式設置密碼:
    這也是最常用的方式,通過用戶名和密碼的方式來管理權限,設置權限的格式爲:
setAcl path digest:user:pwd:crwda
setAcl 路徑節點 驗證模式:用戶名:密碼:權限

下面是一個例子:
1.使用加密機制生成base64位密碼,因爲不能用明文密碼,可以直接百度base64在線編碼解碼,或者使用如下方式:

java -cp ./zookeeper-3.4.6.jar:./lib/log4j-1.2.16.jar:./lib/slf4j-log4j12-1.6.1.jar:./lib/slf4j-api-1.6.1.jar:zookeeper-3.4.9.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider test:test
// 生成的"test"的密碼爲:test:test->test:V28q/NynI4JI3Rk54h0r8O5kMug=

在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

當通過addauth輸入對應的驗證之後,對於一次client對znode進行操作驗證ACL的方式爲:

  • 對每個ACL,首先操作類型與權限匹配(意思是開始是不會直接匹配用戶名和密碼的,而是當執行ACL中限制的操作時纔會進行下一步匹配)
  • 只有匹配權限成功才進行session的auth信息與ACL的用戶名和密碼匹配
  • 如果兩次都匹配成功,則允許操作,否則返回error權限不夠

超級管理員superdigest
    這是對於忘記密碼的拯救措施,修改zkServer.sh,加入super權限設置

zkServer -Dzookeeper.DigestAuthenticationProvider.superDigest=super:gG7s8t3oDEtIqF6DM9LlI/R+9Ss=

然後重啓服務,然後再客戶端使用命令addauth digest super:super

如果znode ACL List中任何一個ACL都沒有setAcl權限,那麼就算superDigest也修改不了它的權限;再假如這個znode還不開放delete權限,那麼它的所有子節點都將不會被刪除。唯一的辦法是通過手動刪除snapshot和log的方法,將ZK回滾到一個以前的狀態,然後重啓,當然這會影響到該znode以外其它節點的正常應用。

缺陷:
ACL畢竟僅僅是訪問控制,並非完善的權限管理,通過這種方式做多集羣隔離,還有很多侷限性:

  1. ACL並無遞歸機制,任何一個znode創建後,都需要單獨設置ACL,無法繼承父節點的ACL設置。
  2. 除了ip這種scheme,digest和auth的使用對用戶都不是透明的,這也給使用帶來了很大的成本,很多依賴zookeeper的開源框架也沒有加入對ACL的支持,例如hbase,storm。
節點類型

Zookeeper中又把節點分爲兩種類型:臨時節點永久節點。節點的類型在創建節點的時候就必須被確定,並且不能改變。

  • 臨時節點(EPHEMERAL ):其生命週期依賴於創建他們的會話(Session),一旦會話結束,臨時節點將會被自動刪除,當然也可以手動刪除,雖然每個臨時節點的Znode都會被綁定到一個客戶端會話,但他們對所有的客戶端還是可見的。另外,臨時節點不允許創建子節點。
  • 永久節點(PERSISTENT):該節點的生命週期不依賴於會話,並且只有在客戶端顯式的執行刪除操作時,它們纔會被刪除。

關於會話(session):
Client與Zookeeper服務端之間的通信,需要創建一個Session,這個Session會有一個超時時間,因爲服務端會把Client的session信息持久化,所以在session沒超時之前,它們之間的連接在各個服務端之間可以透明的移動。
在客戶端和服務端之間通過發送心跳來進行連接,每個一定時間就會發送一個心跳,通過心跳來傳遞信息,也通過心跳來判斷客戶端是否斷開連接,這個超時時間是配置信息可以設置的。
在這裏插入圖片描述

順序節點

    當創建Znode節點時,客戶端可以請求Zookeeper的路徑結尾添加一個遞增的計數,這個計數對於此節點的父節點來說是唯一的,它的格式爲“%10d”(10位數字,沒有用0補充)

觀察者

    客戶端可以在節點上設置watch,也稱爲監視器,當節點狀態發生改變時(Znode的增刪改)將會觸發watch所對應的操作。(關於具體的觸發事件,參見後面的 Watch觸發器
    當watch被觸發時,Zookeeper會向客戶端發送且僅發送一條通知,因爲watch只能被觸發一次,這樣可以減少網絡流量。

Zookeeper節點的屬性

在這裏插入圖片描述
在這裏插入圖片描述

Zookeeper服務中的操作

基本操作有9個:
在這裏插入圖片描述
說明:

  • 更新ZooKeeper操作是有限制的。delete或setData必須明確要更新的Znode的版本號,我們可以調用exists找到。如果版本號不匹配,更新將會失敗。
  • 更新ZooKeeper操作是非阻塞式的。因此客戶端如果失去了一個更新(由於另一個進程在同時更新這個Znode),他可以在不阻塞其他進程執行的情況下,選擇重新嘗試或進行其他操作。
  • 儘管ZooKeeper可以被看做是一個文件系統,但是處於便利,摒棄了一些文件系統地操作原語。因爲文件非常的小並且使整體讀寫的,所以不需要打開、關閉或是尋地的操作。

Watch觸發器

(相關示例參見Zookeeper–客戶端操作

    Zookeeper可以爲所有的讀操作設置watch,這些讀操作包括:exists(),getData()和getChildren()。
    watch事件是一次性的觸發器,當watch的對象狀態發生改變時,將會觸發此對象上的watch所對應的事件,watch事件將被異步發送給客戶端,並且Zookeeper爲watch機制提供了有序的一致性保證。理論上,客戶端接收watch事件的時間快於看到watch對象狀態變化的時間。

watch類型
  • data watch:getData()和exists()負責設置data watch
  • child watch:getChildren()負責設置child watch

我們可以通過操作返回的數據來設置不同的watch:

  1. getData()和exists():返回節點的數據信息
  2. getChildren():返回子節點列表信息

於是:

  • 一個成功的setData操作將觸發Znode的data watch
  • 一個成功的create節點操作將觸發Znode的data watch和child watch
  • 一個成功的delete節點操作將觸發Znode的data watch和child watch

    可能這樣理解會有些難以理解,這樣解釋就會明白了,觸發器的設置是在上述的兩種類型的三種方法中,但是並不是調用這些方法就會觸發watch,不是這樣的,而是調用這些方法返回的數據信息在前後時間段裏不一樣,就會觸發,
    也就是說,數據信息發生了修改時纔會觸發,拿getData()爲例,當創建一個節點(create),更新數據(setData)以及刪除節點(delete)時,數據信息發生變化,而因此,是在調用這些操作的時候纔會觸發watch操作。

註冊與觸發

在這裏插入圖片描述

  • exists操作上的watch:在被監視的Znode創建,刪除或更新數據時被觸發
  • getData()操作上的watch:在被監視的Znode刪除或更新數據時被觸發,在被創建時不能被觸發,因爲只有Znode一定存在時,getData操作纔會成功。
  • getChildren()操作上的watch:在被監視的Znode的子節點創建或刪除,或是這個Znode節點本身被刪除時被觸發。NodeDelete表示Znode被刪除,NodeDeletedChange表示子節點被刪除,可以通過查看watch的事件類型來查看上述兩個刪除。

注意:
Zookeeper的watch實際上要處理兩類事件(都是通過重載process(WatchEvent event)方法實現):
1.連接狀態時間(type=None && path = null)
這類時間不需要註冊,也不需要我們連續觸發,只需要處理就行
2.節點事件
節點的建立,刪除,數據的修改,它是one time trigger,我們需要不停的註冊觸發,還可能發生事件丟失的情況
節點時間的觸發,通過方法exists,getData或getChildren()來處理這類方法時,有雙重作用:
1.註冊觸發事件
2.方法本身的功能,即可以用異步的回調方法來實現,通過重載processResult()來處理方法本身。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章