2018年第16周-ZooKeeper基本概念(配搭建過程和Master-Workers例子)

背景

隨着計算機的硬件和操做系統二者相輔相成地發展,從早期的ENIAC計算機到如今的x86的計算機,從之前的單一控制終端(Single Operator, Single Console, SOSC)的操做系統到如今百花爭鳴的操做系統(如MacOS、Windows、Linux等),現代的操做系統發展還有一個最重要的特徵是網絡的出現,網絡促進了網絡操做性系統和分佈式操做系統的出現。對於網絡操做系統來講,其任務是將多個計算機虛擬成一個計算機。傳統的網絡操做系統是在現有操做系統的基礎上增長網絡功能,而分佈式操做系統則是從一開始就把對多計算機的支持考慮進來,是從新設計的操做系統,因此比網絡操做系統效率高。分佈式操做系統除了提供傳統操做系統的功能外,還提供多計算機協做的功能。 node

根據上述的發展,咱們在其基礎上構建的應用也分爲集中式系統和分佈式系統。集中式系統具備明顯的單點問題。大型主機雖然在性能和穩定性方面表現卓越,但這不表明它永遠不會出問題。另外隨着業務的不斷髮展,用戶訪問迅速提升,計算機系統的規模也在不斷擴大,在單一大型主機上進行的系統的擴容每每比較困難。 算法

按照正常的套路來講,下一步應該是說隨着PC的性能不斷提高和網絡技術的快速普及,因此不少企業就開始去掉大型機,從而改用普通PC來做爲分佈式的計算機系統。這樣說就太平淡了,企業也不是這麼容易說變就變,就算谷歌開始的時候敢用可靠性較低的服務器的一個重要緣由,在於它是最初並不爲用戶提供存儲和計算服務,服務器都是本身內部使用。若是計算到一半死機了,那就再從死機的斷點從新啓動計算。甚至若是一開始有些中間數據丟失了,那就再產生一遍。這時它的可靠性不像銀行那樣重要。2004年當谷歌開始提供Gmail服務,爲了確保用戶的數據不丟失,它採用了3x3九臺服務器存一組數據,這個成本就不低了。同時期,雅虎等公司採用兩臺可靠性的服務器存儲郵件。這時期,谷歌的Gmail是很是賠錢的。後來谷歌的雲存儲部門用軟件實現了5臺服務器分佈式存儲,達到過去九臺服務器的可靠性,Gmail的成本才降下來。谷歌千方百計用廉價服務器集羣取代超級計算機的過程遠比不少人想象的複雜。這種想法其實早在谷歌以前就有了,可是其它公司就是由於沒法解決其中的不少技術細節問題,使得采用大量低可靠性的服務器帶來的好處尚未它帶來的麻煩多,最終都放棄了。(這裏引伸出谷歌的文化之一)谷歌把事作到了極致,克服了各類技術困難,最後作成了。數組

企業吸取已證明可行的經驗是很快速,因而後來亞馬遜等公司也實現了廉價服務器集羣取代超級計算機的功能。其中在國內,最爲典型的就是阿里巴巴的「去IOE」活動,安全

也正是在這個大背景之下,還有在大數據和雲計算驅動之下,ZooKeeper在雅虎裏誕生了。用於解決不少分佈式系統下的問題。當設計一個使用ZooKeeper的應用時,最好就是將應用數據和協調數據給分開。如郵件系統,用戶只關心他們的郵箱內容,而不須要知道哪臺郵箱服務器在處理。所以在這裏郵箱內容就是應用數據,而協調數據則是具體是哪臺郵件服務器在處理。服務器

分佈式的問題

因爲分佈式系統概念有不少種,在這裏咱們定義爲:一個系統由多個組件組成,而每一個組件獨立並同時運行在不一樣的物理機器上。網絡

分佈式系統一誕生就面臨諸多的難題和挑戰。典型的問題有這些:session

  • 通訊失敗(Communication Failure)

因爲分佈系統引入了網絡因素,而網絡自己是不穩定的。所以分佈式系統中各個結點以前進網絡通訊時,會出現消息丟失和消息延遲等現象。架構

  • 網絡分區(Network Partion)

網絡分區,也稱腦裂(split-brain),集羣中部分節點之間不可達而引發的(或者由於節點請求壓力較大,致使其餘節點與該節點的心跳檢測不可用)。當上述狀況發生時,不一樣分裂的小集羣會自主的選擇出master節點,形成本來的集羣會同時存在多個master節點。併發

  • 三態

由於通訊失敗的問題,會帶來其中一個問題是三態,三態即成功、失敗與超時。分佈式系統的每一次請求與響應都會有這三種結果。最麻煩的是超時,由於它存在這兩種可能:
1.因爲通信失敗,該請求(消息)並無被成功地發送到接收方,而是在發送過程當中就丟失了。
2.該請求(消息)成功地被接收方接收後,並進行了處理,可是在將響應反饋給發送方時,發生了消息丟失現象。框架

  • 節點故障

這也是屬於通訊失敗的狀況,但着重點是說,機器自身掛了,沒法發出消息。有多是宕機或負荷嚴重的狀況致使的。

上述分佈式問題致使了一致性問題難以解決,並且在2002年的時候,MIT的Seth Gibert和Nancy Lynch證實的CAP定理。這個定理是:系統不可能同時知足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)。
既然是邏輯證實出來的,那它就必定對的。這也是分佈式系統的邊界或者說是上限。因此人們開始在一致性和可用性上權衡,從而誕生不少算法如2PC、3PC和Paxos算法,這些都影響着ZooKeeper的設計。
ZooKeeper不能解決全部分佈式系統的問題。但它提供了一個很好框架去處理這些問題。
ZooKeeper爲分佈式系統提供了協調功能和控制衝突。協調意思是說幾個進程須要一塊兒作一些事情,例如,在Master-Worker分佈式架構裏,worker通知master它可用,master所以分派任務給它。
而控制衝突則不同:它更可能是這種情形,兩個進程是不能同時進行,一個必須等待一個才能進行。例如,在Master-Worker分佈式架構裏,咱們但願只有一個進程變成master,因此當多個進程同時激活本身爲master時,必須實現互斥( mutual exclusion),只能有一個進程得到鎖,併成爲master角色。

ZooKeeper的API提供了:

  • 強一致性,順序和持久化的保證
  • 提供實現同步的能力
  • 一個更簡單的方法處理分佈式系統中的併發問題。

ZooKeeper的基礎

咱們將圍繞一個例子去講述概念並實踐。這個例子是:Master-Worker分佈式架構。
架構圖以下:

clipboard.png

Master進程的職責時跟蹤Worker和任務,並將任務分派給Worker。爲了實現這個Master-Worker系統,咱們必須解決這三個關鍵問題:
Master故障
若是master故障而且變得不可用,系統就不能分配新任務或從新分配失敗的任務。

Woker故障
若是worker故障,則分配給它的任務則完成不了。

通訊失敗
若是master和worker不能交換信息,則worker可能沒法獲取分派給它的任務。

爲了解決上述問題,這個系統必須有如下功能:能在一個master掛掉以後,從新選擇一個新的master;判斷那些woker是可用的;當worker與master由於網絡分區失去與master鏈接時可以從新分派任務;當一個節點得到鎖以後發生網絡分區或掛掉時,需讓這個鎖失效。

從新分配任務有如下狀況:若是任務可重複執行,則能夠無需任何校驗的把這任務從新分派。但若是這個任務是不能重複執行的,則須要協調多個worker執行任務的狀況。

znode

ZooKeeper並無直接提供上述的功能,而是提供一個跟文件系統很像的API。這個被組織稱樹結構,每一個結點都存儲很小的數據的結點(不超過1M),被稱爲znode。以下圖,根結點包含4個結點,其中3個又是分支結點,擁有葉子節點。而葉子結點就包含數據。

clipboard.png

不存在的結點在znode裏也是蘊含信息。如在這個Master-Worker例子裏,若是master結點不存在,則代表master還沒選出來。另外根據上圖,咱們能夠看出/workers結點是當前系統中全部可用的woker的父節點。foo.com:2181是worker的信息。若是woker不可用了,就應該把對於的結點從/worers上刪除。
而/tasks結點是父結點,其子結點是已經被建立的任務,且等待被執行。Master-Worker例子裏,客戶端就能夠在/tasks下建立一個結點來表明一個新任務,而且等待該任務的狀態。
最後/assign結點的子結點是全部已經被分派給worker的任務。

znode能夠有和能夠沒有數據。若是有數據,數據類型必須是字節數據(Byte Array)。字節數組的含義解釋就須要各自應用決定,ZooKeeper沒有提供解析這字節數組的功能。

ZooKeeper的命令行提供了一下API:

create /path data

建立一個znode結點名爲/path,包含數據data

delete /path

刪除znode結點/path

exists /path

檢查是否有/path結點

setData /path data

設置/path結點的數據

getData /path

獲取/path結點數據

getChildren /path

獲取/path結點的子結點列表

znode的模式

在建立znode結點時,咱們能夠指定模式(mode),不一樣模式決定znode結點的不一樣行爲:

永久(Persistent)和臨時(Ephemeral)znode結點

znode結點只能是永久結點或者是臨時結點。永久結點/path只能被delete命令刪除。而臨時結點,在建立該結點的客戶端故障了或失去與ZooKeeper失去鏈接時,這個結點會被刪除。
在Master-Worker例子裏,咱們須要維護任務的分派狀況,哪怕master故障了。
臨時znode結點傳遞着這樣的信息:結點的建立者的session有效,則結點的應用才能存在。例如,master的結點在Master-Worker例子裏就是臨時的。master故障時,master結點也不該該存在。一樣的也適合worker的狀況。
由於臨時znode結點在它的建立者的session超時失效時被刪除,則咱們不容許臨時結點擁有子結點。

序列znode結點(Sequantial Znodes)

一個znode能夠被設置爲序列(sequential)。一個序列結點時惟一的,單調遞增的整數。是在path後面追加序列數據。例如,若是一個客戶端建立一個序列znode結點/task/task-,ZooKeeper會分配一個序列,如1,追加到路徑上,則爲/task/task-1。序列znode結點提供一個簡便地方法去建立擁有惟一名字的znode結點。也能夠被用來查看建立znode結點的順序。

總結

所以總的來講,znode有如下四個模式:persistent, ephemeral, persistent_sequential和ephemeral_sequential。

Watch與通知(Watches and Notifications)

因爲是遠程訪問ZooKeeper,因此訪問znode結點是很是昂貴的:高延遲或多無用的操做。考慮如下狀況,若是下圖,第二次使用getChildren /task返回的是一樣的值,所以時沒有必要的。

clipboard.png

這是輪訓(polling)的廣泛出現的問題。咱們使用一種叫通知(notifications)的機制來代理客戶端的輪訓:客戶端在ZooKeeper上註冊接收znode結點變化的通知。指定一個znode結點,接受其一個通知的註冊,這過程叫 設置watch。一個watch是單步操做(one-shot operation),也就是說一個watch僅僅觸發一個通知。若是想接受多個通知,則須要在接受到通知後,從新設置watch。設置watch和接受通知的過程以下圖:

clipboard.png

版本(Version)

每個znode結點都會有一個版本號,這個版本號在每次結點的數據發生改變都會遞增。這樣ZooKeeper的一些API操做就能夠帶上條件,這操做如setData和Delete。設置數據時能夠帶上版本號,版本號匹配不上則操做失敗。操做過程以下圖:

clipboard.png

ZooKeeper的架構

clipboard.png

客戶端能夠經過client庫來與ZooKeeper節點進行通訊。
ZooKeeper服務能夠有兩種模式:單機(standalone)和法定人數(quorum)。單機模式也就是單個服務器,ZooKeeper的狀態不會被複制(replicate)。而在法定人數模式下,則會有一組ZooKeeper服務器,也稱爲ZooKeeper套裝(ZooKeeper ensemble),節點之間會複製ZooKeeper的狀態,並一塊兒提供服務。

ZooKeeper的法定人數

在法定人數模式下,ZooKeeper會冗餘(replicate)它的數據到各個服務器裏。當若是客戶端必須等待每一個服務器都保持好它的數據,才能往下進行操做,則延遲將會難以接受。在現實世界的公共事務管理裏,法定人數是要求出席投票的最低人數。而在ZooKeeper裏,法定人數是保證ZooKeeper可以正常工做,可用,的最少服務器數量。這個最少數量也是保證至少有這麼多臺服務器是同步了數據的。這樣才能保證數據是被安全保存。如咱們用5臺ZooKeeper服務器,則法定人數則爲3臺。只要有3臺服務器保存了數據,客戶端則能夠往下繼續本身的事情,另外兩天服務器最終會同步剛剛保存的數據。

會話(Session)

客戶端在對ZooKeeper發送任何請求以前,都得先與ZooKeeper創建會話(session)。會話這個概念在ZooKeeper裏是很是重要且關鍵的。全部操做都必須關聯着會話。當一個會話由於某些緣由結束時,經過這會話建立的臨時結點也將會伴隨這會話的結束而被刪除。
客戶端是經過TCP鏈接來與服務器通訊,客戶端只能鏈接一個服務器。若是客戶端鏈接的一臺服務器掛了,則session將會移動到下一臺服務器,這個移動過程是透明的,由ZooKeeper負責處理的。
會話提供了有序保證(order guarantees),意思是一個會話裏的操做都是FIFO的順序。但跨session操做時,哪怕session不是重疊,而是連續的不一樣的session,這個FIFO的順序也會被破壞,如:

  • 客戶端創建一個session,並異步的連續的發送兩個操做,create /tasks和/workers.
  • 第一個session失效
  • 客戶端創建其餘session,也發送一個異步請求,create /assign

在這種狀況下,是有可能僅僅只有/tasks和/assign被建立,/workers而沒有被建立。這是由於被交錯的會話給打斷了。

ZooKeeper的命令行體驗

經過命令行來操做ZooKeeper來實現Master-Worker這個例子。

安裝ZooKeeper(quorum模式)

1.下載安裝包zookeeper-3.4.5.tar.gz

2.解壓zookeeper-3.4.5.tar.gz到/usr/local

tar -zxvf zookeeper-3.4.5.tar.gz

3.建立zoo.cfg配置文件

mv conf/zoo_sample.cfg conf/zoo.cfg

4.修改zoo.cfg配置文件,修改以下

#防止把根分區給被佔滿
dataDir=/usr/local/zookeeper-3.4.5/tmp  
clientPort=2181
server.1=zk1.jevoncode.com:2888:3888
server.2=zk2.jevoncode.com:2888:3888
server.3=zk3.jevoncode.com:2888:3888

其中2181是ZooKeeper服務器的開放給客戶端訪問的端口,而2888和3888是ZooKeeper節點之間用於通訊和leader選舉。
5.在各個結點的/usr/local/zookeeper-3.4.5/tmp目錄下創建myid文件,內容爲server的id,如給zk1.jevoncode.com這個結點添加myid

echo "1">/usr/local/zookeeper-3.4.5/tmp/myid

6.在每一個結點啓動ZooKeeper服務

./bin/zkServer.sh start

7.查看每一個結點的狀態,就能夠看到leader和follower了

./bin/zkServer.sh status

實現Master-Worker這個例子

咱們經過zkCli.sh工具來實現Master-Worker這個例子的功能。
Master-Worker這個例子涉及到三個角色:
Master

這個master負責監控新woker和任務,並分派任務給可用的worker。

Worker

worker將本身註冊到該系統中,並保證master可以看到它是可用的,可執行任務,而後監聽新任務。
客戶端
客戶端建立新任務並等待系統的響應

Master角色

1.在窗口A鏈接ZooKeeper

./bin/zkCli.sh -server zk1.jevoncode:2181,zk2.jevoncode:2181,zk3.jevoncode:2181

2.在窗口A建立臨時znode結點/master

create -e /master "master1.jevoncode.com:2223"

3.在窗口B鏈接ZooKeeper

./bin/zkCli.sh -server zk1.jevoncode:2181,zk2.jevoncode:2181,zk3.jevoncode:2181

4.在窗口B建立臨時znode結點/master,模擬兩個進程激活master角色。這個會失敗

#會返回Node already exists: /master
create -e /master "master2.jevoncode.com:2223"

5.在窗口B,既然/master已存在,則需監聽/master,以便當窗口A的master故障時,能快速恢復過來。stat命令是獲取znode屬性,而後後續參數true是設置一個watch在/master上

stat /master true

6.當窗口A退出時,窗口B會收到一下通知:

WatchedEvent state:SyncConnected type:NodeDeleted path:/master

7.此時窗口B就能夠激活本身稱爲master

create -e /master "master2.jevoncode.com:2223"

Worker角色、任務和任務分派

在繼續客戶端和Worker操做以前,咱們須要建立三個父節點,/workers, /tasks和/assign

create /workers ""
create /tasks ""
create /assign ""
ls /

另外Master角色需監聽worker和任務

ls /workers true
ls /tasks true

Worker角色

1.在窗口C鏈接ZooKeeper

./bin/zkCli.sh -server zk1.jevoncode:2181,zk2.jevoncode:2181,zk3.jevoncode:2181

2.建立一個worker結點

create -e /workers/worker1.jevoncode.com "worker1.jevoncode.com:2224"

3.此時Master角色的窗口會受到通知

WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/workers

4.worker需建立一個父節點來接受任務分派,並watch

create /assign/worker1.jevoncode.com ""
ls /assign/worker1.jevoncode.com true

客戶端角色

1.在窗口D鏈接ZooKeeper

./bin/zkCli.sh -server zk1.jevoncode:2181,zk2.jevoncode:2181,zk3.jevoncode:2181

2.提交任務,在這裏是建立一個序列znode結點。

create -s /tasks/task- "cmd"

3.設置一個watch,等待任務完成

ls /task/task-0000000000 true

4.當任務建立是,Master的窗口將會收到通知

WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/tasks

5.此時Master窗口查看任務,查看可用worker並分派任務

ls /tasks
ls /workers
create /assign/worker1.jevoncode.com/task-0000000000 ""

6.此時Worker窗口C收到通知

WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged
path:/assign/worker1.example.com

7.Worker窗口C,查看任務,並執行完任務後修改任務狀態

ls /assign/worker1.jevoncode.com
create /tasks/task-0000000000/status "done"

8.客戶端收到通知,並查看任務完成結果

#通知
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged
path:/tasks/task-0000000000

#查看結果
get /tasks/task-0000000000
相關文章
相關標籤/搜索