摘要:有可能是宕機或負荷嚴重的情況導致的。為分布式系統提供了協調功能和控制沖突。
背景
隨著計算機的硬件和操作系統兩者相輔相成地發展,從早期的ENIAC計算機到現在的x86的計算機,從以前的單一控制終端(Single Operator, Single Console, SOSC)的操作系統到現在百花爭鳴的操作系統(如MacOS、Windows、Linux等),現代的操作系統發展還有一個最重要的特征是網絡的出現,網絡促進了網絡操作性系統和分布式操作系統的出現。對于網絡操作系統來說,其任務是將多個計算機虛擬成一個計算機。傳統的網絡操作系統是在現有操作系統的基礎上增加網絡功能,而分布式操作系統則是從一開始就把對多計算機的支持考慮進來,是重新設計的操作系統,所以比網絡操作系統效率高。分布式操作系統除了提供傳統操作系統的功能外,還提供多計算機協作的功能。
根據上述的發展,我們在其基礎上構建的應用也分為集中式系統和分布式系統。集中式系統具有明顯的單點問題。大型主機雖然在性能和穩定性方面表現卓越,但這不代表它永遠不會出問題。另外隨著業務的不斷發展,用戶訪問迅速提高,計算機系統的規模也在不斷擴大,在單一大型主機上進行的系統的擴容往往比較困難。
按照正常的套路來說,下一步應該是說隨著PC的性能不斷提升和網絡技術的快速普及,所以很多企業就開始去掉大型機,從而改用普通PC來作為分布式的計算機系統。這樣說就太平淡了,企業也不是這么容易說變就變,就算谷歌開始的時候敢用可靠性較低的服務器的一個重要原因,在于它是最初并不為用戶提供存儲和計算服務,服務器都是自己內部使用。如果計算到一半死機了,那就再從死機的斷點重新啟動計算。甚至如果一開始有些中間數據丟失了,那就再產生一遍。這時它的可靠性不像銀行那樣重要。2004年當谷歌開始提供Gmail服務,為了確保用戶的數據不丟失,它采用了3x3九臺服務器存一組數據,這個成本就不低了。同時期,雅虎等公司采用兩臺可靠性的服務器存儲郵件。這時期,谷歌的Gmail是非常賠錢的。后來谷歌的云存儲部門用軟件實現了5臺服務器分布式存儲,達到過去九臺服務器的可靠性,Gmail的成本才降下來。谷歌想方設法用廉價服務器集群取代超級計算機的過程遠比很多人想象的復雜。這種想法其實早在谷歌之前就有了,但是其它公司就是因為無法解決其中的很多技術細節問題,使得采用大量低可靠性的服務器帶來的好處還沒有它帶來的麻煩多,最終都放棄了。(這里引申出谷歌的文化之一)谷歌把事做到了極致,克服了各種技術困難,最后做成了。
企業吸收已證實可行的經驗是很快速,于是后來亞馬遜等公司也實現了廉價服務器集群取代超級計算機的功能。其中在國內,最為典型的就是阿里巴巴的“去IOE”活動,
也正是在這個大背景之下,還有在大數據和云計算驅動之下,ZooKeeper在雅虎里誕生了。用于解決很多分布式系統下的問題。當設計一個使用ZooKeeper的應用時,最好就是將應用數據和協調數據給分開。如郵件系統,用戶只關心他們的郵箱內容,而不需要知道哪臺郵箱服務器在處理。因此在這里郵箱內容就是應用數據,而協調數據則是具體是哪臺郵件服務器在處理。
分布式的問題由于分布式系統概念有很多種,在這里我們定義為:一個系統由多個組件組成,而每個組件獨立并同時運行在不同的物理機器上。
分布式系統一誕生就面臨諸多的難題和挑戰。典型的問題有這些:
通信失敗(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分布式架構。
架構圖如下:
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個又是分支結點,擁有葉子節點。而葉子結點就包含數據。
不存在的結點在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可以被設置為序列(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返回的是同樣的值,因此時沒有必要的。
這是輪訓(polling)的普遍出現的問題。我們使用一種叫通知(notifications)的機制來代理客戶端的輪訓:客戶端在ZooKeeper上注冊接收znode結點變化的通知。指定一個znode結點,接受其一個通知的注冊,這過程叫 設置watch。一個watch是單步操作(one-shot operation),也就是說一個watch僅僅觸發一個通知。如果想接受多個通知,則需要在接受到通知后,重新設置watch。設置watch和接受通知的過程如下圖:
版本(Version)每一個znode結點都會有一個版本號,這個版本號在每次結點的數據發生改變都會遞增。這樣ZooKeeper的一些API操作就可以帶上條件,這操作如setData和Delete。設置數據時可以帶上版本號,版本號匹配不上則操作失敗。操作過程如下圖:
ZooKeeper的架構客戶端可以通過client庫來與ZooKeeper節點進行通信。
ZooKeeper服務可以有兩種模式:單機(standalone)和法定人數(quorum)。單機模式也就是單個服務器,ZooKeeper的狀態不會被復制(replicate)。而在法定人數模式下,則會有一組ZooKeeper服務器,也稱為ZooKeeper套裝(ZooKeeper ensemble),節點之間會復制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 trueWorker角色
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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69195.html
閱讀 797·2023-04-25 22:57
閱讀 3051·2021-11-23 10:03
閱讀 613·2021-11-22 15:24
閱讀 3156·2021-11-02 14:47
閱讀 2901·2021-09-10 11:23
閱讀 3115·2021-09-06 15:00
閱讀 3936·2019-08-30 15:56
閱讀 3322·2019-08-30 15:52