前言:
RDS系統致力于MySQL數據的高可用,高可靠,高性能以及在線擴展功能,實現這些特性的主要邏輯功能都運行在管理服務器上,一旦管理服務器宕機,數據庫的在線擴展功能/備份功能/故障恢復功能等都無從談起。然而,之前RDS系統管理服務器卻是單點服務,為了保證整個系統的穩定性,管理服務器需要實現高可用,結合當前主流的高可用方案,決定使用Zookeeper來實現服務的高可用。
基本設計方案原理:
如下圖所示,管理服務器A B C會在zk的root節點上注冊臨時序列節點/root/manager000000001 /root/manager000000002 /root/manager0000000003,序列最小的節點會被選為leader對外提供服務,其他節點作為熱備節點隨時準備升為leader。在此圖中,A是leader,B C是standby。一旦A服務器因為某些原因宕機,zk就會將該服務器注冊的臨時節點移除掉,然后通知所有其他節點B C,B C會選出序列號最小的節點作為新的leader對外提供服務,此時B就會被選為新主。
血案現場:
會有這么一種比較特殊的場景需要考慮,比如當前leader是A BC都是備機。假如A和zk集群之間的網絡出現了異常,A會收到一個連接狀態被持久化為Disconnected的event,但是ZK Server并沒有在這時移除A注冊的臨時節點,所以理論上A還是leader直至session timeout,session timeout后zk會將A注冊的臨時節點移除掉,然后通知B C選出新的leader,顯而易見,B因為序列號小會成為新的leader。但是問題來了,session timeout的時候A的客戶端并沒有接收到任何notification,換句話說,它依然會認為自己是leader,這個時候就出現了這樣的場景,A認為自己是leader,而B同樣會認為自己是leader,即同時出現兩個leader對外提供服務的情況。這很顯然是不合理的,但如何深入地理解并解決這個問題呢?
個人認為理解并解決這個問題需要理解下面三個子問題:
1. 理解Zookeeper中Session的含義以及Connection Loss和Session Expired的關系
2. 理解Zookeeper中Session為什么由Server維護,而不由Client維護
3. 理解作為leader的A在整個流程中應該如何轉變自己的角色,來避免腦裂
對zookeeper中Connection Loss和Session Expired的理解
Session是指當Client創建一個同Server的連接時產生的會話。連接Connected之后Session狀態就開啟,Zookeeper服務器和Client采用長連接方式(Client會不停地向Server發送心跳)保證session在不出現網絡問題、服務器宕機或Client宕機情況下可以一直存在。因此,在正常情況下,session會一直有效,并且ZK集群上所有機器都會保存這個Session信息。
在ZK中,很多數據和狀態都是和會話綁定的,一旦會話失效,那么ZK就開始清除和這個會話有關的信息,包括這個會話創建的臨時節點和注冊的所有Watcher。
一旦網絡連接因為某種原因斷開或者zk集群發生宕機,ZK Client會馬上捕獲到這個異常,封裝為一個ConnectionLoss的事件,然后啟動自動重連機制在地址列表中選擇新的地址進行重連。重連會有三種結果:
(1)在session timeout時間內重連成功,client會重新收到一個syncconnected的event,并將連接重新持久化為connected狀態
(2)超過session timeout時間段后重連成功,client會收到一個expired的event,并將連接持久化為closed狀態
(3)一直重連不上,client將不會收到任何event
很顯然,無論重連成功與否,在session timeout那個重要的時間點,ZK Client是接收不到任何ZK Server清理臨時節點的信息的。這也就導致ZK會通知了B C節點A已經不再是Leader,A自身卻沒有接收到這樣的信息,依舊對外提供服務,進而產生腦裂的問題。
Zookeeper中Session為什么由Server維護,而不由Client維護
可能很多朋友看了上面的討論就會想為什么ZK Client不維護Session信息,如果這樣做了,ZK Client就會在Session Timeout時得到相應的通知。
好,現在假如這樣實現了,看看會發生什么。設想有這么一種真實場景,某個連接的Session Timeout是15s,ZK集群因為未知原因發生宕機,5min之后集群恢復成功。在Session Timeout時,ZK Client確實可以知道Session失效,然后做降主操作,但是ZK Server卻不知道Session已經失效,也就不會通知其他節點選出新的Leader,此時整個系統實際上處于沒有Leader的狀態。即使5min之后重連成功,因為舊Session對應的臨時節點沒有被清理且序號最小,ZK依然會認為Leader是該臨時節點,而實際上該臨時節點對應不到任何的ZK Client,所以這種情況下系統依然選不出Leader。
可見,如果由Client維護Session,在某些場景下(網絡異常或者集群宕機時間超過Session Timeout),由于邏輯問題根本選不出Leader。
因此這種方案是不可行的。
那能不能從應用層面避免腦裂問題呢?帶著問題進入下個部分。
避免腦裂問題:作為leader的A在整個流程中應該如何轉變自己的角色
因為ZK本身的設計使得這種場景下沒有一個完美的解決方案,可以考慮采用退化的方案進行處理。
A在接收到DisConnected事件后就降主,不對外提供服務。然后等待接下來的發生的事情,首先可能發生的事件是在Session Timeout時間段內重連成功得到SyncConnected事件,這時A可以重新升級為主,對外提供服務。如果這段時間內沒有重連成功,ZK Server在Session Timeout時會將A注冊的臨時節點移除,并通知B和C A已經停止對外服務了,需要選出新的leader。因為A自己已經降主了,所以在選出新leader后也不會出現多主現象。如果A在Session Timeout時間段外重連又成功了,那此時肯定新的leader已經選出來了,A需要重新注冊作為新的備機候選。
可以使用如下的流程圖解釋這個過程:
這種應用層面的方案在一定程度上解決了腦裂問題,但是會出現一段時間系統無Leader的情況,持續時間最長為Session超時時間。
總結:
結合上述分析,可以針對Session Timeout的問題得出一個應用層面的解決方案,即Client分階段轉變角色方案。如下圖所示,箭頭上方紅色標示表示Client所處不同階段,箭頭下方藍色標示表示Client在不同階段的角色轉變。
本文章為作者原創
🈲禁止🈲
其他公眾賬號轉載,若有轉載,請標明出處
文章列表