1.1.1 Zookeeper的保證node
l 順序性,client的updates請求都會根據它發出的順序被順序的處理;算法
l 原子性, 一個update操做要麼成功要麼失敗,沒有其餘可能的結果;數據庫
l 一致的鏡像,client不論鏈接到哪一個server,展現給它都是同一個視圖;緩存
l 可靠性,一旦一個update被應用就被持久化了,除非另外一個update請求更新了當前值服務器
l 實時性,對於每一個client它的系統視圖都是最新的網絡
1.1.2 Zookeeper server角色session
領導者(Leader) : 領導者不接受client的請求,負責進行投票的發起和決議,最終更新狀態。分佈式
跟隨者(Follower): Follower用於接收客戶請求並返回客戶結果。參與Leader發起的投票。性能
觀察者(observer): Oberserver能夠接收客戶端鏈接,將寫請求轉發給leader節點。可是Observer不參加投票過程,只是同步leader的狀態。Observer爲系統擴展提供了一種方法。學習
學習者 ( Learner ) : 和leader進行狀態同步的server統稱Learner,上述Follower和Observer都是Learner。
1.1.3 Zookeeper集羣
一般Zookeeper由2n+1臺servers組成,每一個server都知道彼此的存在。每一個server都維護的內存狀態鏡像以及持久化存儲的事務日誌和快照。對於2n+1臺server,只要有n+1臺(大多數)server可用,整個系統保持可用。
系統啓動時,集羣中的server會選舉出一臺server爲Leader,其它的就做爲follower(這裏先不考慮observer角色)。接着由follower來服務client的請求,對於不改變系統一致性狀態的讀操做,由follower的本地內存數據庫直接給client返回結果;對於會改變系統狀態的更新操做,則交由Leader進行提議投票,超過半數經過後返回結果給client。
二.Zookeeper server工做原理
Zookeeper的核心是原子廣播,這個機制保證了各個server之間的同步。實現這個機制的協議叫作Zab協議。Zab協議有兩種模式,它們分別是恢復模式和廣播模式。當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數server的完成了和leader的狀態同步之後,恢復模式就結束了。狀態同步保證了leader和server具備相同的系統狀態。
一旦leader已經和多數的follower進行了狀態同步後,他就能夠開始廣播消息了,即進入廣播狀態。這時候當一個server加入zookeeper服務中,它會在恢復模式下啓動,發現leader,並和leader進行狀態同步。待到同步結束,它也參與消息廣播。Zookeeper服務一直維持在Broadcast狀態,直到leader崩潰了或者leader失去了大部分的followers支持。
Broadcast模式極其相似於分佈式事務中的2pc(two-phrase commit 兩階段提交):即leader提起一個決議,由followers進行投票,leader對投票結果進行計算決定是否經過該決議,若是經過執行該決議(事務),不然什麼也不作。
廣播模式須要保證proposal被按順序處理,所以zk採用了遞增的事務id號(zxid)來保證。全部的提議(proposal)都在被提出的時候加上了zxid。實現中zxid是一個64爲的數字,它高32位是epoch用來標識leader關係是否改變,每次一個leader被選出來,它都會有一個新的epoch。低32位是個遞增計數。
當leader崩潰或者leader失去大多數的follower,這時候zk進入恢復模式,恢復模式須要從新選舉出一個新的leader,讓全部的server都恢復到一個正確的狀態。
首先看一下選舉的過程,zk的實現中用了基於paxos算法(主要是fastpaxos)的實現。具體以下:
1.每一個Server啓動之後都詢問其它的Server它要投票給誰。
2.對於其餘server的詢問,server每次根據本身的狀態都回複本身推薦的leader的id和上一次處理事務的zxid(系統啓動時每一個server都會推薦本身)
3.收到全部Server回覆之後,就計算出zxid最大的哪一個Server,並將這個Server相關信息設置成下一次要投票的Server。
4.計算這過程當中得到票數最多的的sever爲獲勝者,若是獲勝者的票數超過半數,則改server被選爲leader。不然,繼續這個過程,直到leader被選舉出來。
此外恢復模式下,若是是從新剛從崩潰狀態恢復的或者剛啓動的的server還會從磁盤快照中恢復數據和會話信息。(zk會記錄事務日誌並按期進行快照,方便在恢復時進行狀態恢復)
選完leader之後,zk就進入狀態同步過程。
1.leader就會開始等待server鏈接
2.Follower鏈接leader,將最大的zxid發送給leader
3.Leader根據follower的zxid肯定同步點
4.完成同步後通知follower 已經成爲uptodate狀態
5.Follower收到uptodate消息後,又能夠從新接受client的請求進行服務了。
三. ZookeeperServer工做流程
3.1.1 主線程的工做:
1. 剛開始時各個Server處於一個平等的狀態peer
2. 主線程加載配置後啓動。
3. 主線程啓動QuorumPeer線程,該線程負責管理多數協議(Quorum),並根據表決結果進行角色的狀態轉換。
4. 而後主線程等待QuorumPeer線程。
3.1.2 QuorumPeer線程
1. 首先會從磁盤恢復zkdatabase(內存數據庫),並進行快照回覆。
2. 而後啓動server的通訊線程,準備接收client的請求。
3. 緊接着該線程進行選舉leader準備,選擇選舉算法,啓動response線程(根據自身狀態)向其餘server回覆推薦的leaer。
4. 剛開始的時候server都處於looking狀態,進行選舉根據選舉結果設置本身的狀態和角色。
3.1.3 quorumPeer有幾種狀態
1. Looking: 尋找狀態,這個狀態不知道誰是leader,會發起leader選舉
2. Observing: 觀察狀態,這時候observer會觀察leader是否有改變,而後同步leader的狀態
3. Following: 跟隨狀態,接收leader的proposal ,進行投票。並和leader進行狀態同步
4. Leading: 領導狀態,對Follower的投票進行決議,將狀態和follower進行同步
當一個Server發現選舉的結果本身是Leader把本身的狀態改爲Leading,若是Server推薦了其餘人爲Server它將本身的狀態改爲Following。作Leader的server若是發現擁有的follower少於半數時,它從新進入looking狀態,從新進行leader選舉過程。(Observing狀態是根據配置設置的)。
3.2 Leader的工做流程:
3.2.1 Leader主線程:
1.首先leader開始恢復數據和清除session
啓動zk實例,創建請求處理鏈(Leader的請求處理鏈):PrepRequestProcessor->ProposalRequestProcessor->CommitProcessor->Leader.ToBeAppliedRequestProcessor ->FinalRequestProcessor
2.獲得一個新的epoch,標識一個新的leader , 並得到最大zxid(方便進行數據同步)
3.創建一個學習者接受線程(來接受新的followers的鏈接,follower鏈接後肯定followers的zxvid號,來肯定是須要對follower進行什麼同步措施,好比是差別同步(diff),仍是截斷(truncate)同步,仍是快照同步)
4. 向follower創建一個握手過程leader->follower NEWLEADER消息,並等待直到多數server發送了ack
5. Leader不斷的查看已經同步了的follower數量,若是同步數量少於半數,則回到looking狀態從新進行leaderElection過程,不然繼續step5.
3.2.2 LearnerCnxAcceptor線程
1.該線程監聽Learner的鏈接
2.接受Learner請求,併爲每一個Learner建立一個LearnerHandler來服務
3.2.3 LearnerHandler線程的服務流程
1.檢查server來的第一個包是否爲follower.info或者observer.info,若是不是則沒法創建握手。
2. 獲得Learner的zxvid,對比自身的zxvid,肯定同步點
3.和Learner創建第二次握手,向Learner發送NEWLEADER消息
4.與server進行數據同步。
5.同步結束,知會server同步已經ok,能夠接收client的請求。
6. 不斷讀取follower消息判斷消息類型
i. 若是是LEADER.ACK,記錄follower的ack消息,超過半數ack,將proposal提交(Commit)
ii. 若是是LEADER.PING,則維持session(延長session失效時間)
iii. 若是是LEADER.REQEST,則將request放入請求鏈進行處理–Leader寫請求發起proposal,而後根據follower回覆的結果來肯定是否commit的。最後由FinallRequestProcessor來實際進行持久化,並回覆信息給相應的response給server
3.3 Follower的工做流程:
1.啓動zk實例,創建請求處理鏈:FollowerRequestProcessor->CommitProcessor->FinalProcessor
2.follower首先會鏈接leader,並將zxid和id發給leader
3.接收NEWLEADER消息,完成握手過程。
4.同leader進行狀態同步
5.完成同步後,follower能夠接收client的鏈接
5.接收到client的請求,根據請求類型
l 對於寫操做, FollowerRequestProcessor會將該操做做爲LEADER.REQEST發給LEADER由LEADER發起投票。
l 對於讀操做,則經過請求處理鏈的最後一環FinalProcessor將結果返回給客戶端
對於observer的流程再也不贅述,observer流程和Follower的惟一不一樣的地方就是observer不會參加leader發起的投票。
三.關於Zookeeper的擴展
爲了提升吞吐量一般咱們只要增長服務器到Zookeeper集羣中。可是當服務器增長到必定程度,會致使投票的壓力增大從而使得吞吐量下降。所以咱們引出了一個角色:Observer。
Observers 的需求源於 ZooKeeper follower服務器在上述工做流程中實際扮演了兩個角色。它們從客戶端接受鏈接與操做請求,以後對操做結果進行投票。這兩個職能在 ZooKeeper集羣擴展的時候彼此制約。若是咱們但願增長 ZooKeeper 集羣服務的客戶數量(咱們常常考慮到有上萬個客戶端的狀況),那麼咱們必須增長服務器的數量,來支持這麼多的客戶端。然而,從一致性協議的描述能夠看到,增長服務器的數量增長了對協議的投票部分的壓力。領導節點必須等待集羣中過半數的服務器響應投票。因而,節點的增長使得部分計算機運行較慢,從而拖慢整個投票過程的可能性也隨之提升,投票操做的會隨之降低。這正是咱們在實際操做中看到的問題——隨着 ZooKeeper 集羣變大,投票操做的吞吐量會降低。
因此須要增長客戶節點數量的指望和咱們但願保持較好吞吐性能的指望間進行權衡。要打破這一耦合關係,引入了不參與投票的服務器,稱爲 Observers。 Observers 能夠接受客戶端的鏈接,將寫請求轉發給領導節點。可是,領導節點不會要求 Observers 參加投票。相反,Observers 不參與投票過程,僅僅和其餘服務節點一塊兒獲得投票結果。
這個簡單的擴展給 ZooKeeper 的可伸縮性帶來了全新的鏡像。咱們如今能夠加入不少 Observers 節點,而無須擔憂嚴重影響寫吞吐量。規模伸縮並不是無懈可擊——協議中的一歩(通知階段)仍然與服務器的數量呈線性關係。可是,這裏的穿行開銷很是低。所以能夠認爲在通知服務器階段的開銷沒法成爲主要瓶頸。
上圖顯示了一個簡單評測的結果。縱軸是從一個單一的客戶端發出的每秒鐘同步寫操做的數量。橫軸是 ZooKeeper 集羣的尺寸。藍色的是每一個服務器都是 voting 服務器的狀況,而綠色的則只有三個是 voting 服務器,其它都是 Observers。圖中看到,擴充 Observers,寫性能幾乎能夠保持不變,但若是同時擴展 voting 節點的數量的話,性能會明顯降低。顯然 Observers 是有效的。所以Observer能夠用於提升Zookeeper的伸縮性。
此外Observer還能夠成爲特定場景下,廣域網部署的一種方案。緣由有三點:1.爲了得到更好的讀性能,須要讓客戶端足夠近,但若是將投票服務器分佈在兩個數據中心,投票的延遲太大會大幅下降吞吐,是不可取的。所以但願可以不影響投票過程,將投票服務器放在同一個IDC進行部署,Observer能夠跨IDC部署。2. 投票過程當中,Observer和leader之間的消息、要遠小於投票服務器和server的消息,這樣遠程部署對帶寬要求就較小。3.因爲Observers即便失效也不會影響到投票集羣,這樣若是數據中心間鏈路發生故障,不會影響到服務自己的可用性。這種故障的發生機率要遠高於一個數據中心中機架間的鏈接的故障機率,因此不依賴於這種鏈路是個優勢。
Znodes
每一個節點在zookeeper中用znode表示。znodes 包含數據變動和acl變動的版本號。znode一樣包含時間戳。版本號和時間戳用來幫助zookeeper驗證緩存或者協調更新。每次znode數據發生變化都會使版本號增長。例如,每次client接受數據時都會接收到數據的版本號。當client更新或者刪除數據時必須給znode提供數據的版本號。若是提供的版本號與實際的版本號不匹配,更新操做會失敗。
znode是程序訪問的主要實體類。包含以下特性:
Watches
clients 能夠爲znode設置watch。znode發生改變將會觸發watch。當一個watch觸發,zookeeper會向client發送通知。
Data Access
在namespace中存儲在每一個znode上的數據發生的讀寫操做都是原子性的。讀一個znode上的所有數據或者替換掉所有數據都是原子性的。每一個znode都有一個Access Contron List(ACL)用來約束哪些人能夠執行相應操做。
Zookeeper不是用來作數據庫或者存貯大對象的。相反,它只負責協調數據。數據能夠來自配置表單、結構化信息等等。這些數據的有一個共同的特色那就是都很小:以Kb爲測量單位。Zookeeper的client和server的實現類都會驗證znode存儲的數據是否小於1M,可是數據應該比平均值小的多。操做大數據將會觸發一些消耗時間的額外操做而且影響潛在的操做,由於須要額外的時間在網絡和存儲介質上轉移數據。若是有大數據須要存儲,一般的辦法是把這些數據存儲在專門的大型文件系統上,例如NFS或者HDFS,而後把存儲介質的位置存在zookeeper上。
Ephemeral Nodes
zookeeper有一種znode是ephemeral nodes。這些znode只在session存在期間有效。當session結束的時候這些ephemeral nodes被刪除。因此ephemeral znodes不能有子節點。
Sequence Nodes -- Unique Naming
當建立一個znode時候,你也能夠要求zookeeper在path的結尾單調遞增。計數器對每個znode來講都是惟一的。計數器使用%010d格式化--例如<path>0000000001。注意:計數器使用一個singed int(4bytes)來存儲下一個序列值。因此計數器達到2147483647 後會溢出。
zookeeper的每一個節點能夠有以下三種角色:
1.leader和follower
ZooKeeper須要在全部的服務(能夠理解爲服務器)中選舉出一個Leader,而後讓這個Leader來負責管理集羣。此時,集羣中的其它服務器則成爲此Leader的Follower。而且,當Leader故障的時候,須要ZooKeeper可以快速地在Follower中選舉出下一個Leader。這就是ZooKeeper的Leader機制,下面咱們將簡單介紹在ZooKeeper中,Leader選舉(Leader Election)是如何實現的。
此操做實現的核心思想是:首先建立一個EPHEMERAL目錄節點,例如「/election」。而後。每個ZooKeeper服務器在此目錄下建立一個SEQUENCE|EPHEMERAL類型的節點,例如「/election/n_」。在SEQUENCE標誌下,ZooKeeper將自動地爲每個ZooKeeper服務器分配一個比前一個分配的序號要大的序號。此時建立節點的ZooKeeper服務器中擁有最小序號編號的服務器將成爲Leader。
在實際的操做中,還須要保障:當Leader服務器發生故障的時候,系統可以快速地選出下一個ZooKeeper服務器做爲Leader。一個簡單的解決方案是,讓全部的follower監視leader所對應的節點。當Leader發生故障時,Leader所對應的臨時節點將會自動地被刪除,此操做將會觸發全部監視Leader的服務器的watch。這樣這些服務器將會收到Leader故障的消息,並進而進行下一次的Leader選舉操做。可是,這種操做將會致使「從衆效應」的發生,尤爲當集羣中服務器衆多而且帶寬延遲比較大的時候,此種狀況更爲明顯。
在Zookeeper中,爲了不從衆效應的發生,它是這樣來實現的:每個follower對follower集羣中對應的比本身節點序號小一號的節點(也就是全部序號比本身小的節點中的序號最大的節點)設置一個watch。只有當follower所設置的watch被觸發的時候,它才進行Leader選舉操做,通常狀況下它將成爲集羣中的下一個Leader。很明顯,此Leader選舉操做的速度是很快的。由於,每一次Leader選舉幾乎只涉及單個follower的操做。
2.Observer
observer的行爲在大多數狀況下與follower徹底一致, 可是他們不參加選舉和投票, 而僅僅接受(observing)選舉和投票的結果.