天天看點

為什麼不使用ZooKeeper建構雲平台發現服務?

本文作者通過zookeeper與eureka作為 service發現服務(注:webservices 體系中的uddi就是個發現服務)的優劣對比,分享了knewton在雲計算平台部署服務的經驗。本文雖然略顯偏激,但是看得出knewton在雲平台方面是非常有經驗的,這篇文章從實踐角度出發分别從雲平台特點、cap原理以及運維三個方面對比了zookeeper與eureka兩個系統作為釋出服務的優劣,并提出了在雲平台建構發現服務的方法論。

為什麼不使用ZooKeeper建構雲平台發現服務?

背景

很多公司選擇使用 zookeeper作為service發現服務(service discovery),但是在建構 knewton(knewton 是一個提供個性化教育平台的公司、學校和出版商可以通過knewton平台為學生提供自适應的學習材料)平台時,我們發現這是個根本性的錯誤。在這邊文章 中,我們将用我們在實踐中遇到的問題來說明,為什麼使用zookeeper做service發現服務是個錯誤。

請留意服務部署環境

讓我們從頭開始梳理。我們在部署服務的時候,應該首先考慮服務部署的平台(平台環境),然後才能考慮平台上跑的軟體 系統或者如何在標明的平台上自己建構一套系統。例如,對于雲部署平台來說,平台在硬體層面的伸縮(注:作者應該指的是系統的備援性設計,即系統遇到單點失 效問題,能夠快速切換到其他節點完成任務)與如何應對網絡故障是首先要考慮的。當你的服務運作在大量伺服器建構的叢集之上時(注:原話為大量可替換設 備),則肯定會出現單點故障的問題。對于knewton來說,我們雖然是部署在aws上的,但是在過往的運維中,我們也遇到過形形色色的故障;是以,你應 該把系統設計成“故障開放型”(expecting failure)的。其實有很多同樣使用aws的 公司跟我們遇到了(同時有很多 書是介紹這方面的)相似的問題。你必須能夠提前預料到平台可能會出現的問題如:意外故障(注:原文為box failure,隻能意會到作者指的是意外彈出的錯誤提示框),高延遲與 網絡分割問題(注:原文為network partitions。意思是當網絡交換機出故障會導緻不同子網間通訊中斷)——同時我們要能建構足夠彈性的系統來應對它們的發生。

永遠不要期望你部署服務的平台跟其他人是一樣的!當然,如果你在獨自運維一個資料中心,你可能會花很多時間與錢來避免硬體故障與網絡分割問題,這 是另一種情況了;但是在雲計算平台中,如aws,會産生不同的問題以及不同的解決方式。當你實際使用時你就會明白,但是,你最好提前應對它們(注:指的是 上一節說的意外故障、高延遲與網絡分割問題)的發生。

zookeeper作為發現服務的問題

zookeeper(注:zookeeper是著名hadoop的一個子項目,旨在解決大規模分 布式應用場景下,服務協調同步(coordinate service)的問題;它可以為同在一個分布式系統中的其他服務提供:統一命名服務、配置管理、分布式鎖服務、叢集管理等功能)是個偉大的開源項目,它 很成熟,有相當大的社群來支援它的發展,而且在生産環境得到了廣泛的使用;但是用它來做service發現服務解決方案則是個錯誤。

在分布式系統領域有個著名的 cap定理(c- 資料一緻性;a-服務可用性;p-服務對網絡分區故障的容錯性,這三個特性在任何分布式系統中不能同時滿足,最多同時滿足兩個);zookeeper是個 cp的,即任何時刻對zookeeper的通路請求能得到一緻的資料結果,同時系統對網絡分割具備容錯性;但是它不能保證每次服務請求的可用性(注:也就 是在極端環境下,zookeeper可能會丢棄一些請求,消費者程式需要重新請求才能獲得結果)。但是别忘了,zookeeper是分布式協調服務,它的 職責是保證資料(注:配置資料,狀态資料)在其管轄下的所有服務之間保持同步、一緻;是以就不難了解為什麼zookeeper被設計成cp而不是ap特性 的了,如果是ap的,那麼将會帶來恐怖的後果(注:zookeeper就像交叉路口的信号燈一樣,你能想象在交通要道突然信号燈失靈的情況嗎?)。而且, 作為zookeeper的核心實作算法 zab,就是解決了分布式系統下資料如何在多個服務之間保持同步問題的。

作為一個分布式協同服務,zookeeper非常好,但是對于service發現服務來說就不合适了;因為對于service發現服務來說就算是 傳回了包含不實的資訊的結果也比什麼都不傳回要好;再者,對于service發現服務而言,甯可傳回某服務5分鐘之前在哪幾個伺服器上可用的資訊,也不能 因為暫時的網絡故障而找不到可用的伺服器,而不傳回任何結果。是以說,用zookeeper來做service發現服務是肯定錯誤的,如果你這麼用就慘 了!

而且更何況,如果被用作service發現服務,zookeeper本身并沒有正确的處理網絡分割的問題;而在雲端,網絡分割問題跟其他類型的故障一樣的确會發生;是以最好提前對這個問題做好100%的準備。就像 jepsen在 zookeeper網站上釋出的部落格中所說:在zookeeper中,如果在同一個網絡分區(partition)的節點數(nodes)數達不到 zookeeper選取leader節點的“法定人數”時,它們就會從zookeeper中斷開,當然同時也就不能提供service發現服務了。

如果給zookeeper加上用戶端緩存(注:給zookeeper節點配上本地緩存)或者其他類似技術的話可以緩解zookeeper因為網絡故障造成節點同步資訊錯誤的問題。 pinterest與 airbnb公 司就使用了這個方法來防止zookeeper故障發生。這種方式可以從表面上解決這個問題,具體地說,當部分或者所有節點跟zookeeper斷開的情況 下,每個節點還可以從本地緩存中擷取到資料;但是,即便如此,zookeeper下所有節點不可能保證任何時候都能緩存所有的服務注冊資訊。如果 zookeeper下所有節點都斷開了,或者叢集中出現了網絡分割的故障(注:由于交換機故障導緻交換機底下的子網間不能互訪);那麼zookeeper 會将它們都從自己管理範圍中剔除出去,外界就不能通路到這些節點了,即便這些節點本身是“健康”的,可以正常提供服務的;是以導緻到達這些節點的服務請求 被丢失了。(注:這也是為什麼zookeeper不滿足cap中a的原因)

更深層次的原因是,zookeeper是按照cp原則建構的,也就是說它能保證每個節點的資料保持一緻,而為zookeeper加上緩存的做法的 目的是為了讓zookeeper變得更加可靠(available);但是,zookeeper設計的本意是保持節點的資料一緻,也就是cp。是以,這樣 一來,你可能既得不到一個資料一緻的(cp)也得不到一個高可用的(ap)的service發現服務了;因為,這相當于你在一個已有的cp系統上強制栓了 一個ap的系統,這在本質上就行不通的!一個service發現服務應該從一開始就被設計成高可用的才行!

如果抛開cap原理不管,正确的設定與維護zookeeper服務就非常的困難;錯誤會 經常發生, 導緻很多工程被建立隻是為了減輕維護zookeeper的難度。這些錯誤不僅存在與用戶端而且還存在于zookeeper伺服器本身。knewton平台 很多故障就是由于zookeeper使用不當而導緻的。那些看似簡單的操作,如:正确的重建觀察者(reestablishing watcher)、用戶端session與異常的處理與在zk視窗中管理記憶體都是非常容易導緻zookeeper出錯的。同時,我們确實也遇到過 zookeeper的一些經典bug: zookeeper-1159 與 zookeeper-1576; 我們甚至在生産環境中遇到過zookeeper選舉leader節點失敗的情況。這些問題之是以會出現,在于zookeeper需要管理與保障所管轄服務 群的session與網絡連接配接資源(注:這些資源的管理在分布式系統環境下是極其困難的);但是它不負責管理服務的發現,是以使用zookeeper當 service發現服務得不償失。

做出正确的選擇:eureka的成功

我們把service發現服務從zookeeper切換到了eureka平台,它是一個開 源的服務發現解決方案,由netflix公司開發。(注:eureka由兩個元件組成:eureka伺服器和eureka用戶端。eureka伺服器用作 服務注冊伺服器。eureka用戶端是一個java用戶端,用來簡化與伺服器的互動、作為輪詢負載均衡器,并提供服務的故障切換支援。)eureka一開 始就被設計成高可用與可伸縮的service發現服務,這兩個特點也是netflix公司開發所有平台的兩個特色。( 他們都在讨論eureka)。自從切換工作開始到現在,我們實作了在生産環境中所有依賴于eureka的産品沒有下線維護的記錄。我們也被告知過,在雲平台做服務遷移注定要遇到失敗;但是我們從這個例子中得到的經驗是,一個優秀的service發現服務在其中發揮了至關重要的作用!

首先,在eureka平台中,如果某台伺服器當機,eureka不會有類似于zookeeper的選舉leader的過程;用戶端請求會自動切換 到新的eureka節點;當當機的伺服器重新恢複後,eureka會再次将其納入到伺服器叢集管理之中;而對于它來說,所有要做的無非是同步一些新的服務 注冊資訊而已。是以,再也不用擔心有“掉隊”的伺服器恢複以後,會從eureka伺服器叢集中剔除出去的風險了。eureka甚至被設計用來應付範圍更廣 的網絡分割故障,并實作“0”當機維護需求。當網絡分割故障發生時,每個eureka節點,會持續的對外提供服務(注:zookeeper不會):接收新 的服務注冊同時将它們提供給下遊的服務發現請求。這樣一來,就可以實作在同一個子網中(same side of partition),新釋出的服務仍然可以被發現與通路。

但是,eureka做到的不止這些。正常配置下,eureka内置了心跳服務,用于淘汰一些“瀕死”的伺服器;如果在eureka中注冊的服務, 它的“心跳”變得遲緩時,eureka會将其整個剔除出管理範圍(這點有點像zookeeper的做法)。這是個很好的功能,但是當網絡分割故障發生時, 這也是非常危險的;因為,那些因為網絡問題(注:心跳慢被剔除了)而被剔除出去的伺服器本身是很”健康“的,隻是因為網絡分割故障把eureka叢集分割 成了獨立的子網而不能互訪而已。

幸運的是,netflix考慮到了這個缺陷。如果eureka服務節點在短時間裡丢失了大量的心跳連接配接(注:可能發生了網絡故障),那麼這個 eureka節點會進入”自我保護模式“,同時保留那些“心跳死亡“的服務注冊資訊不過期。此時,這個eureka節點對于新的服務還能提供注冊服務,對 于”死亡“的仍然保留,以防還有用戶端向其發起請求。當網絡故障恢複後,這個eureka節點會退出”自我保護模式“。是以eureka的哲學是,同時保 留”好資料“與”壞資料“總比丢掉任何”好資料“要更好,是以這種模式在實踐中非常有效。

最後,eureka還有用戶端緩存功能(注:eureka分為用戶端程式與伺服器端程式兩個部分,用戶端程式負責向外提供注冊與發現服務接口)。 是以即便eureka叢集中所有節點都失效,或者發生網絡分割故障導緻用戶端不能通路任何一台eureka伺服器;eureka服務的消費者仍然可以通過 eureka用戶端緩存來擷取現有的服務注冊資訊。甚至最極端的環境下,所有正常的eureka節點都不對請求産生相應,也沒有更好的伺服器解決方案來解 決這種問題時;得益于eureka的用戶端緩存技術,消費者服務仍然可以通過eureka用戶端查詢與擷取注冊服務資訊,這點很重要。

eureka的構架保證了它能夠成為service發現服務。它相對與zookeeper來說剔除了leader節點的選取或者事務日志機制,這 樣做有利于減少使用者維護的難度也保證了eureka的在運作時的健壯性。而且eureka就是為發現服務所設計的,它有獨立的用戶端程式庫,同時提供心 跳服務、服務健康監測、自動釋出服務與自動重新整理緩存的功能。但是,如果使用zookeeper你必須自己來實作這些功能。eureka的所有庫都是開源 的,所有人都能看到與使用這些源代碼,這比那些隻有一兩個人能看或者維護的用戶端庫要好。

維護eureka伺服器也非常的簡單,比如,切換一個節點隻需要在現有eip下移除一個現有的節點然後添加一個新的就行。eureka提供了一個 web-based的圖形化的運維界面,在這個界面中可以檢視eureka所管理的注冊服務的運作狀态資訊:是否健康,運作日志等。eureka甚至提供 了restful-api接口,友善第三方程式內建eureka的功能。

結論

關于service發現服務通過本文我們想說明兩點:1、留意服務運作的硬體平台;2、時刻關注你要解決的問題,然後決定 使用什麼平台。knewton就是從這兩個方面考慮使用eureka替換zookeeper來作為service發現服務的。雲部署平台是充滿不可靠性 的,eureka可以應對這些缺陷;同時service發現服務必須同時具備高可靠性與高彈性,eureke就是我們想要的!

本文作者:佚名

來源:51cto