ZooKeeper系列(8):ZooKeeper伸縮性
一、ZooKeeper中Observer
1.1 ZooKeeper角色
經過前面的介紹,我想大家都已經知道了在ZooKeeper集群當中有兩種角色Leader和Follower。Leader可以接受client 請求,也接收其他Server轉發的寫請求,負責更新系統狀態。 Follower也可以接收client請求,如果是寫請求將轉發給Leader來更新系統狀態,讀請求則由Follower的內存數據庫直接響應。 ZooKeeper集群如圖1.1所示。
圖 1.1 ZooKeeper集群服務
但在ZooKeeper的3.3.3版本以後,ZooKeeper中又添加了一種新角色Observer。Observer的作用同Follower類似,唯一區別就是它不參與選主過程。那麽,我們就可以根據該特性將ZK集群中的Server分為兩種:
(1) 投票Server:Leader、Follower
(2) 非投票Server:Observer
1.2 為什麽引入Observer
(1) ZooKeeper可伸縮性
那麽,ZooKeeper為什麽要引入Observer這個角色呢?其實在ZooKeeper中引入Observer,主要是為了使 ZooKeeper具有更好的可伸縮性。那麽,何為可伸縮性?關於伸縮性,對於不同的人意味著不同的事情。 而在這裏是說,如果我們的工作負載可以通過給系統分配更多的資源來分擔,那麽這個系統就是可伸縮的;一個不可伸縮的系統卻無法通過增加資源來提升性能,甚 至會在工作負載增加時,性能會急劇下降。
在Observer出現以前,ZooKeeper的伸縮性由Follower來實現,我們可以通過添加Follower節點的數量來保證 ZooKeeper服務的讀性能。但是隨著Follower節點數量的增加,ZooKeeper服務的寫性能受到了影響。為什麽會出現這種情況?在此,我 們需要首先了解一下這個"ZK服務"是如何工作的。
(2) ZK服務過程
ZooKeeper服務中的每個Server可服務於多個Client,並且Client可連接到ZK服務中的任一臺Server來提交請求。若是讀請求,則由每臺Server的本地副本數據庫直接響應。若是改變Server狀態的寫請求,需要通過一致性協議來處理,這個協議就是我們前面介紹的Zab協議。
簡單來說,Zab協議規定:來自Client的所有寫請求,都要轉發給ZK服務中唯一的Server—Leader, 由Leader根據該請求發起一個Proposal。然後,其他的Server對該Proposal進行Vote。之後,Leader對Vote進行收 集,當Vote數量過半時Leader會向所有的Server發送一個通知消息。最後,當Client所連接的Server收到該消息時,會把該操作更新 到內存中並對Client的寫請求做出回應。該工作流程如下圖1.2所示。
圖1.2 ZK 寫請求工作流程圖
從圖中我們可以看出, ZooKeeper 服務器在上述協議中實際扮演了兩個職能。它們一方面從客戶端接受連接與操作請求,另一方面對操作結果進行投票。這兩個職能在 ZooKeeper集群擴展的時候彼此制約。例如,當我們希望增加 ZK服務中Client數量的時候,那麽我們就需要增加Server的數量,來支持這麽多的客戶端。然而,從Zab協議對寫請求的處理過程中我們可以發 現,增加服務器的數量,則增加了對協議中投票過程的壓力。因為Leader節點必須等待集群中過半Server響應投票,於是節點的增加使得部分計算機運 行較慢,從而拖慢整個投票過程的可能性也隨之提高,寫操作也會隨之下降。這正是我們在實際操作中看到的問題——隨著 ZooKeeper 集群變大,寫操作的吞吐量會下降。
(3) ZooKeeper擴展
所以,我們不得不,在增加Client數量的期望和我們希望保持較好吞吐性能的期望間進行權衡。要打破這一耦合關系,我們引入了不參與投票的服務 器,稱為 Observer。 Observer可以接受客戶端的連接,並將寫請求轉發給Leader節點。但是,Leader節點不會要求 Observer參加投票。相反,Observer不參與投票過程,僅僅在上述第3歩那樣,和其他服務節點一起得到投票結果。
圖 1.3 Observer 寫吞吐量測試
圖1.3 顯示了一個簡單評測的結果。縱軸是,單一客戶端能夠發出的每秒鐘同步寫操作的數量。橫軸是 ZooKeeper 集群的尺寸。藍色的是每個服務器都是投票Server的情況,而綠色的則只有三個是投票Server,其它都是 Observer。從圖中我們可以看出,我們在擴充 Observer時寫性能幾乎可以保持不便。但是,如果擴展投票Server的數量,寫性能會明顯下降,顯然 Observers 是有效的。
這個簡單的擴展,給 ZooKeeper 的可伸縮性帶來了全新的鏡像。我們現在可以加入很多 Observer 節點,而無須擔心嚴重影響寫吞吐量。但他並非是無懈可擊的,因為協議中的通知階段,仍然與服務器的數量呈線性關系。但是,這裏的串行開銷非常低。因此,我 們可以認為在通知服務器階段的開銷無法成為主要瓶頸。
二、Observer應用
(1) Observer提升讀性能的可伸縮性
應對Client的數量增加,是 Observer的一個重要用例,但是實際上它還給集群帶來很多其它的好處。Observer作為ZooKeeper的一個優化,Observer服務器可以直接獲取Leader的本地數據存儲,而無需經過投票過程。但這也面臨一定的"時光旅行"風險,也就是說:可能在讀到新值之後又讀到老值。但這只在服務器故障時才會發生事實上,在這種情況下,Client可以通過"sync"操作來保證下一個值是最新的。
因此,在大量讀操作的工作負載下,Observer會使ZooKeeper的性能得到巨大提升。若要增加投票Server數量來承擔讀操作,那麽就 會影響ZooKeeper服務的寫性能。而且Observer允許我們將讀性能和寫性能分開,這使ZooKeeper更適用於一些以讀為主的應用場景。
(2) Observer提供了廣域網能力
Observer還能做更多。Observer對於跨廣域網連接的Client來說是很好的候選方案。Observer可作為候選方案,原因有三:
① 為了獲得很好的讀性能,有必要讓客戶端離服務器盡量近,這樣往返時延不會太高。然而,將 ZooKeeper 集群分散到兩個集群是非常不可取的設計,因為良好配置的 ZooKeeper 應該讓投票服務器間用低時延連接互連——否則,我們將會遇到上面提到的低反映速度的問題。
② 而Observer 可以被部署在,需要訪問 ZooKeeper 的任意數據中心中。這樣,投票協議不會受到數據中心間鏈路的高時延的影響,性能得到提升。投票過程中 Observer 和領導節點間的消息遠少於投票服務器和領導節點間的消息。這有助於在遠程數據中心高寫負載的情況下降低帶寬需求。
③ 由於Observer即使失效也不會影響到投票集群,這樣如果數據中心間鏈路發生故障,不會影響到服務本身的可用性。這種故障的發生概率要遠高於一個數據中心中機架間的連接的故障概率,所以不依賴於這種鏈路是個優點。
三、ZooKeeper集群搭建案例
前面介紹了ZooKeeper集群中的幾種角色,接下來給大家來介紹一下如何利用這些角色,來搭建一個性能良好的ZooKeeper集群。我以一個項目為例,給大家分析一下該如何規劃我們的ZooKeeper集群。
假設我們的項目需要進行跨機房操作,我們的總部機房設在杭州,但他還要同美國,青島等多個機房之間進行數據交互。但機房之間的網絡延遲都比較大,比如中美機房走海底光纜有ping操作200ms的延遲,杭州和青島機房有70ms的延遲。 為了提升系統的網絡性能,我們在部署ZooKeeper網絡時會在每個機房部署節點,多個機房之間再組成一個大的網絡,來保證整個ZK集群數據一致性。
根據前面的介紹,最後的部署結構就會是:
(總部) 杭州機房 >=3臺 :由Leader/Follower構成的投票集群
(分支) 青島機房 >=1臺 :由Observer構成的ZK集群
(分支) 美國機房 >=1臺 : 由Observer構成的ZK集群
圖 3.1 ZooKeeper集群部署圖
從圖中我們可以看出,我們在單個機房內組成一個投票集群,外圍的機房都會是一個Observer集群和投票集群進行數據交互。 至於這樣部署的一些好處,大家自己根據我前面對ZooKeeper角色的介紹,對比著體會一下,我想這樣更能幫助大家理解ZooKeeper。而且針對這 樣的部署結構,我們會引入一個優先集群問題: 比如在美國機房的Client,需要優先去訪問本機房的ZK集群,訪問不到才去訪問HZ(總部)機房。
ZooKeeper系列(8):ZooKeeper伸縮性