轉自:http://www.yanyufly.com/2011/04/29/%e8%bd%aczookeeper%e7%9a%84%e5%8e%9f%e7%90%86%e4%bb%8b%e7%bb%8d/
第一章 Zookeeper server
1.1 Zookeeper基本原理
1.1.1 Zookeeper的保證
l 順序性,client的updates請求都會根據它發出的順序被順序的處理;
l 原子性, 一個update操作要么成功要么失敗,沒有其他可能的結果;
l 一致的鏡像,client不論連接到哪個server,展示給它都是同一個視圖;
l 可靠性,一旦一個update被應用就被持久化了,除非另一個update請求更新了當前值
l 實時性,對於每個client它的系統視圖都是最新的
1.1.2 Zookeeper server角色
領導者(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即使失效也不會影響到投票集群,這樣如果數據中心間鏈路發生故障,不會影響到服務本身的可用性。這種故障的發生概率要遠高於一個數據中心中機架間的連接的故障概率,所以不依賴於這種鏈路是個優點。