【寫(xiě)在前面】
本文作者通過(guò)ZooKeeper與Eureka作為 Service發(fā)現(xiàn)服務(wù)(注:WebServices 體系中的UDDI就是個(gè)發(fā)現(xiàn)服務(wù))的優(yōu)劣對(duì)比,分享了Knewton在云計(jì)算平臺(tái)部署服務(wù)的經(jīng)驗(yàn)。本文雖然略顯偏激,但是看得出Knewton在云平臺(tái)方面是非常有經(jīng)驗(yàn)的,這篇文章從實(shí)踐角度出發(fā)分別從云平臺(tái)特點(diǎn)、CAP原理以及運(yùn)維三個(gè)方面對(duì)比了ZooKeeper與Eureka兩個(gè)系統(tǒng)作為發(fā)布服務(wù)的優(yōu)劣,并提出了在云平臺(tái)構(gòu)建發(fā)現(xiàn)服務(wù)的方法論。
背景
很多公司選擇使用 ZooKeeper作為Service發(fā)現(xiàn)服務(wù)(Service Discovery),但是在構(gòu)建 Knewton(Knewton 是一個(gè)提供個(gè)性化教育平臺(tái)的公司、學(xué)校和出版商可以通過(guò)Knewton平臺(tái)為學(xué)生提供自適應(yīng)的學(xué)習(xí)材料)平臺(tái)時(shí),我們發(fā)現(xiàn)這是個(gè)根本性的錯(cuò)誤。在這邊文章 中,我們將用我們?cè)趯?shí)踐中遇到的問(wèn)題來(lái)說(shuō)明,為什么使用ZooKeeper做Service發(fā)現(xiàn)服務(wù)是個(gè)錯(cuò)誤。
請(qǐng)留意服務(wù)部署環(huán)境
讓我們從頭開(kāi)始梳理。我們?cè)诓渴鸱?wù)的時(shí)候,應(yīng)該首先考慮服務(wù)部署的平臺(tái)(平臺(tái)環(huán)境),然后才能考慮平臺(tái)上跑的軟件 系統(tǒng)或者如何在選定的平臺(tái)上自己構(gòu)建一套系統(tǒng)。例如,對(duì)于云部署平臺(tái)來(lái)說(shuō),平臺(tái)在硬件層面的伸縮(注:作者應(yīng)該指的是系統(tǒng)的冗余性設(shè)計(jì),即系統(tǒng)遇到單點(diǎn)失 效問(wèn)題,能夠快速切換到其他節(jié)點(diǎn)完成任務(wù))與如何應(yīng)對(duì)網(wǎng)絡(luò)故障是首先要考慮的。當(dāng)你的服務(wù)運(yùn)行在大量服務(wù)器構(gòu)建的集群之上時(shí)(注:原話為大量可替換設(shè) 備),則肯定會(huì)出現(xiàn)單點(diǎn)故障的問(wèn)題。對(duì)于knewton來(lái)說(shuō),我們雖然是部署在AWS上的,但是在過(guò)往的運(yùn)維中,我們也遇到過(guò)形形色色的故障;所以,你應(yīng) 該把系統(tǒng)設(shè)計(jì)成“故障開(kāi)放型”(expecting failure)的。其實(shí)有很多同樣使用AWS的 公司跟我們遇到了(同時(shí)有很多 書(shū)是介紹這方面的)相似的問(wèn)題。你必須能夠提前預(yù)料到平臺(tái)可能會(huì)出現(xiàn)的問(wèn)題如:意外故障(注:原文為box failure,只能意會(huì)到作者指的是意外彈出的錯(cuò)誤提示框),高延遲與 網(wǎng)絡(luò)分割問(wèn)題(注:原文為network partitions。意思是當(dāng)網(wǎng)絡(luò)交換機(jī)出故障會(huì)導(dǎo)致不同子網(wǎng)間通訊中斷)——同時(shí)我們要能構(gòu)建足夠彈性的系統(tǒng)來(lái)應(yīng)對(duì)它們的發(fā)生。
永遠(yuǎn)不要期望你部署服務(wù)的平臺(tái)跟其他人是一樣的!當(dāng)然,如果你在獨(dú)自運(yùn)維一個(gè)數(shù)據(jù)中心,你可能會(huì)花很多時(shí)間與錢(qián)來(lái)避免硬件故障與網(wǎng)絡(luò)分割問(wèn)題,這 是另一種情況了;但是在云計(jì)算平臺(tái)中,如AWS,會(huì)產(chǎn)生不同的問(wèn)題以及不同的解決方式。當(dāng)你實(shí)際使用時(shí)你就會(huì)明白,但是,你最好提前應(yīng)對(duì)它們(注:指的是 上一節(jié)說(shuō)的意外故障、高延遲與網(wǎng)絡(luò)分割問(wèn)題)的發(fā)生。
ZooKeeper作為發(fā)現(xiàn)服務(wù)的問(wèn)題
ZooKeeper(注:ZooKeeper是著名Hadoop的一個(gè)子項(xiàng)目,旨在解決大規(guī)模分 布式應(yīng)用場(chǎng)景下,服務(wù)協(xié)調(diào)同步(Coordinate Service)的問(wèn)題;它可以為同在一個(gè)分布式系統(tǒng)中的其他服務(wù)提供:統(tǒng)一命名服務(wù)、配置管理、分布式鎖服務(wù)、集群管理等功能)是個(gè)偉大的開(kāi)源項(xiàng)目,它 很成熟,有相當(dāng)大的社區(qū)來(lái)支持它的發(fā)展,而且在生產(chǎn)環(huán)境得到了廣泛的使用;但是用它來(lái)做Service發(fā)現(xiàn)服務(wù)解決方案則是個(gè)錯(cuò)誤。
在分布式系統(tǒng)領(lǐng)域有個(gè)著名的 CAP定理(C- 數(shù)據(jù)一致性;A-服務(wù)可用性;P-服務(wù)對(duì)網(wǎng)絡(luò)分區(qū)故障的容錯(cuò)性,這三個(gè)特性在任何分布式系統(tǒng)中不能同時(shí)滿足,最多同時(shí)滿足兩個(gè));ZooKeeper是個(gè) CP的,即任何時(shí)刻對(duì)ZooKeeper的訪問(wèn)請(qǐng)求能得到一致的數(shù)據(jù)結(jié)果,同時(shí)系統(tǒng)對(duì)網(wǎng)絡(luò)分割具備容錯(cuò)性;但是它不能保證每次服務(wù)請(qǐng)求的可用性(注:也就 是在極端環(huán)境下,ZooKeeper可能會(huì)丟棄一些請(qǐng)求,消費(fèi)者程序需要重新請(qǐng)求才能獲得結(jié)果)。但是別忘了,ZooKeeper是分布式協(xié)調(diào)服務(wù),它的 職責(zé)是保證數(shù)據(jù)(注:配置數(shù)據(jù),狀態(tài)數(shù)據(jù))在其管轄下的所有服務(wù)之間保持同步、一致;所以就不難理解為什么ZooKeeper被設(shè)計(jì)成CP而不是AP特性 的了,如果是AP的,那么將會(huì)帶來(lái)恐怖的后果(注:ZooKeeper就像交叉路口的信號(hào)燈一樣,你能想象在交通要道突然信號(hào)燈失靈的情況嗎?)。而且, 作為ZooKeeper的核心實(shí)現(xiàn)算法 Zab,就是解決了分布式系統(tǒng)下數(shù)據(jù)如何在多個(gè)服務(wù)之間保持同步問(wèn)題的。
作為一個(gè)分布式協(xié)同服務(wù),ZooKeeper非常好,但是對(duì)于Service發(fā)現(xiàn)服務(wù)來(lái)說(shuō)就不合適了;因?yàn)閷?duì)于Service發(fā)現(xiàn)服務(wù)來(lái)說(shuō)就算是 返回了包含不實(shí)的信息的結(jié)果也比什么都不返回要好;再者,對(duì)于Service發(fā)現(xiàn)服務(wù)而言,寧可返回某服務(wù)5分鐘之前在哪幾個(gè)服務(wù)器上可用的信息,也不能 因?yàn)闀簳r(shí)的網(wǎng)絡(luò)故障而找不到可用的服務(wù)器,而不返回任何結(jié)果。所以說(shuō),用ZooKeeper來(lái)做Service發(fā)現(xiàn)服務(wù)是肯定錯(cuò)誤的,如果你這么用就慘 了!
而且更何況,如果被用作Service發(fā)現(xiàn)服務(wù),ZooKeeper本身并沒(méi)有正確的處理網(wǎng)絡(luò)分割的問(wèn)題;而在云端,網(wǎng)絡(luò)分割問(wèn)題跟其他類型的故障一樣的確會(huì)發(fā)生;所以最好提前對(duì)這個(gè)問(wèn)題做好100%的準(zhǔn)備。就像 Jepsen在 ZooKeeper網(wǎng)站上發(fā)布的博客中所說(shuō):在ZooKeeper中,如果在同一個(gè)網(wǎng)絡(luò)分區(qū)(partition)的節(jié)點(diǎn)數(shù)(nodes)數(shù)達(dá)不到 ZooKeeper選取Leader節(jié)點(diǎn)的“法定人數(shù)”時(shí),它們就會(huì)從ZooKeeper中斷開(kāi),當(dāng)然同時(shí)也就不能提供Service發(fā)現(xiàn)服務(wù)了。
如果給ZooKeeper加上客戶端緩存(注:給ZooKeeper節(jié)點(diǎn)配上本地緩存)或者其他類似技術(shù)的話可以緩解ZooKeeper因?yàn)榫W(wǎng)絡(luò)故障造成節(jié)點(diǎn)同步信息錯(cuò)誤的問(wèn)題。 Pinterest與 Airbnb公 司就使用了這個(gè)方法來(lái)防止ZooKeeper故障發(fā)生。這種方式可以從表面上解決這個(gè)問(wèn)題,具體地說(shuō),當(dāng)部分或者所有節(jié)點(diǎn)跟ZooKeeper斷開(kāi)的情況 下,每個(gè)節(jié)點(diǎn)還可以從本地緩存中獲取到數(shù)據(jù);但是,即便如此,ZooKeeper下所有節(jié)點(diǎn)不可能保證任何時(shí)候都能緩存所有的服務(wù)注冊(cè)信息。如果 ZooKeeper下所有節(jié)點(diǎn)都斷開(kāi)了,或者集群中出現(xiàn)了網(wǎng)絡(luò)分割的故障(注:由于交換機(jī)故障導(dǎo)致交換機(jī)底下的子網(wǎng)間不能互訪);那么ZooKeeper 會(huì)將它們都從自己管理范圍中剔除出去,外界就不能訪問(wèn)到這些節(jié)點(diǎn)了,即便這些節(jié)點(diǎn)本身是“健康”的,可以正常提供服務(wù)的;所以導(dǎo)致到達(dá)這些節(jié)點(diǎn)的服務(wù)請(qǐng)求 被丟失了。(注:這也是為什么ZooKeeper不滿足CAP中A的原因)
更深層次的原因是,ZooKeeper是按照CP原則構(gòu)建的,也就是說(shuō)它能保證每個(gè)節(jié)點(diǎn)的數(shù)據(jù)保持一致,而為ZooKeeper加上緩存的做法的 目的是為了讓ZooKeeper變得更加可靠(available);但是,ZooKeeper設(shè)計(jì)的本意是保持節(jié)點(diǎn)的數(shù)據(jù)一致,也就是CP。所以,這樣 一來(lái),你可能既得不到一個(gè)數(shù)據(jù)一致的(CP)也得不到一個(gè)高可用的(AP)的Service發(fā)現(xiàn)服務(wù)了;因?yàn)椋@相當(dāng)于你在一個(gè)已有的CP系統(tǒng)上強(qiáng)制栓了 一個(gè)AP的系統(tǒng),這在本質(zhì)上就行不通的!一個(gè)Service發(fā)現(xiàn)服務(wù)應(yīng)該從一開(kāi)始就被設(shè)計(jì)成高可用的才行!
如果拋開(kāi)CAP原理不管,正確的設(shè)置與維護(hù)ZooKeeper服務(wù)就非常的困難;錯(cuò)誤會(huì) 經(jīng)常發(fā)生, 導(dǎo)致很多工程被建立只是為了減輕維護(hù)ZooKeeper的難度。這些錯(cuò)誤不僅存在與客戶端而且還存在于ZooKeeper服務(wù)器本身。Knewton平臺(tái) 很多故障就是由于ZooKeeper使用不當(dāng)而導(dǎo)致的。那些看似簡(jiǎn)單的操作,如:正確的重建觀察者(reestablishing watcher)、客戶端Session與異常的處理與在ZK窗口中管理內(nèi)存都是非常容易導(dǎo)致ZooKeeper出錯(cuò)的。同時(shí),我們確實(shí)也遇到過(guò) ZooKeeper的一些經(jīng)典bug: ZooKeeper-1159 與 ZooKeeper-1576; 我們甚至在生產(chǎn)環(huán)境中遇到過(guò)ZooKeeper選舉Leader節(jié)點(diǎn)失敗的情況。這些問(wèn)題之所以會(huì)出現(xiàn),在于ZooKeeper需要管理與保障所管轄服務(wù) 群的Session與網(wǎng)絡(luò)連接資源(注:這些資源的管理在分布式系統(tǒng)環(huán)境下是極其困難的);但是它不負(fù)責(zé)管理服務(wù)的發(fā)現(xiàn),所以使用ZooKeeper當(dāng) Service發(fā)現(xiàn)服務(wù)得不償失。
做出正確的選擇:Eureka的成功
我們把Service發(fā)現(xiàn)服務(wù)從ZooKeeper切換到了Eureka平臺(tái),它是一個(gè)開(kāi) 源的服務(wù)發(fā)現(xiàn)解決方案,由Netflix公司開(kāi)發(fā)。(注:Eureka由兩個(gè)組件組成:Eureka服務(wù)器和Eureka客戶端。Eureka服務(wù)器用作 服務(wù)注冊(cè)服務(wù)器。Eureka客戶端是一個(gè)java客戶端,用來(lái)簡(jiǎn)化與服務(wù)器的交互、作為輪詢負(fù)載均衡器,并提供服務(wù)的故障切換支持。)Eureka一開(kāi) 始就被設(shè)計(jì)成高可用與可伸縮的Service發(fā)現(xiàn)服務(wù),這兩個(gè)特點(diǎn)也是Netflix公司開(kāi)發(fā)所有平臺(tái)的兩個(gè)特色。( 他們都在討論Eureka)。自從切換工作開(kāi)始到現(xiàn)在,我們實(shí)現(xiàn)了在生產(chǎn)環(huán)境中所有依賴于Eureka的產(chǎn)品沒(méi)有下線維護(hù)的記錄。我們也被告知過(guò),在云平臺(tái)做服務(wù)遷移注定要遇到失敗;但是我們從這個(gè)例子中得到的經(jīng)驗(yàn)是,一個(gè)優(yōu)秀的Service發(fā)現(xiàn)服務(wù)在其中發(fā)揮了至關(guān)重要的作用!
首先,在Eureka平臺(tái)中,如果某臺(tái)服務(wù)器宕機(jī),Eureka不會(huì)有類似于ZooKeeper的選舉leader的過(guò)程;客戶端請(qǐng)求會(huì)自動(dòng)切換 到新的Eureka節(jié)點(diǎn);當(dāng)宕機(jī)的服務(wù)器重新恢復(fù)后,Eureka會(huì)再次將其納入到服務(wù)器集群管理之中;而對(duì)于它來(lái)說(shuō),所有要做的無(wú)非是同步一些新的服務(wù) 注冊(cè)信息而已。所以,再也不用擔(dān)心有“掉隊(duì)”的服務(wù)器恢復(fù)以后,會(huì)從Eureka服務(wù)器集群中剔除出去的風(fēng)險(xiǎn)了。Eureka甚至被設(shè)計(jì)用來(lái)應(yīng)付范圍更廣 的網(wǎng)絡(luò)分割故障,并實(shí)現(xiàn)“0”宕機(jī)維護(hù)需求。當(dāng)網(wǎng)絡(luò)分割故障發(fā)生時(shí),每個(gè)Eureka節(jié)點(diǎn),會(huì)持續(xù)的對(duì)外提供服務(wù)(注:ZooKeeper不會(huì)):接收新 的服務(wù)注冊(cè)同時(shí)將它們提供給下游的服務(wù)發(fā)現(xiàn)請(qǐng)求。這樣一來(lái),就可以實(shí)現(xiàn)在同一個(gè)子網(wǎng)中(same side of partition),新發(fā)布的服務(wù)仍然可以被發(fā)現(xiàn)與訪問(wèn)。
但是,Eureka做到的不止這些。正常配置下,Eureka內(nèi)置了心跳服務(wù),用于淘汰一些“瀕死”的服務(wù)器;如果在Eureka中注冊(cè)的服務(wù), 它的“心跳”變得遲緩時(shí),Eureka會(huì)將其整個(gè)剔除出管理范圍(這點(diǎn)有點(diǎn)像ZooKeeper的做法)。這是個(gè)很好的功能,但是當(dāng)網(wǎng)絡(luò)分割故障發(fā)生時(shí), 這也是非常危險(xiǎn)的;因?yàn)椋切┮驗(yàn)榫W(wǎng)絡(luò)問(wèn)題(注:心跳慢被剔除了)而被剔除出去的服務(wù)器本身是很”健康“的,只是因?yàn)榫W(wǎng)絡(luò)分割故障把Eureka集群分割 成了獨(dú)立的子網(wǎng)而不能互訪而已。
幸運(yùn)的是,Netflix考慮到了這個(gè)缺陷。如果Eureka服務(wù)節(jié)點(diǎn)在短時(shí)間里丟失了大量的心跳連接(注:可能發(fā)生了網(wǎng)絡(luò)故障),那么這個(gè) Eureka節(jié)點(diǎn)會(huì)進(jìn)入”自我保護(hù)模式“,同時(shí)保留那些“心跳死亡“的服務(wù)注冊(cè)信息不過(guò)期。此時(shí),這個(gè)Eureka節(jié)點(diǎn)對(duì)于新的服務(wù)還能提供注冊(cè)服務(wù),對(duì) 于”死亡“的仍然保留,以防還有客戶端向其發(fā)起請(qǐng)求。當(dāng)網(wǎng)絡(luò)故障恢復(fù)后,這個(gè)Eureka節(jié)點(diǎn)會(huì)退出”自我保護(hù)模式“。所以Eureka的哲學(xué)是,同時(shí)保 留”好數(shù)據(jù)“與”壞數(shù)據(jù)“總比丟掉任何”好數(shù)據(jù)“要更好,所以這種模式在實(shí)踐中非常有效。
最后,Eureka還有客戶端緩存功能(注:Eureka分為客戶端程序與服務(wù)器端程序兩個(gè)部分,客戶端程序負(fù)責(zé)向外提供注冊(cè)與發(fā)現(xiàn)服務(wù)接口)。 所以即便Eureka集群中所有節(jié)點(diǎn)都失效,或者發(fā)生網(wǎng)絡(luò)分割故障導(dǎo)致客戶端不能訪問(wèn)任何一臺(tái)Eureka服務(wù)器;Eureka服務(wù)的消費(fèi)者仍然可以通過(guò) Eureka客戶端緩存來(lái)獲取現(xiàn)有的服務(wù)注冊(cè)信息。甚至最極端的環(huán)境下,所有正常的Eureka節(jié)點(diǎn)都不對(duì)請(qǐng)求產(chǎn)生相應(yīng),也沒(méi)有更好的服務(wù)器解決方案來(lái)解 決這種問(wèn)題時(shí);得益于Eureka的客戶端緩存技術(shù),消費(fèi)者服務(wù)仍然可以通過(guò)Eureka客戶端查詢與獲取注冊(cè)服務(wù)信息,這點(diǎn)很重要。
Eureka的構(gòu)架保證了它能夠成為Service發(fā)現(xiàn)服務(wù)。它相對(duì)與ZooKeeper來(lái)說(shuō)剔除了Leader節(jié)點(diǎn)的選取或者事務(wù)日志機(jī)制,這 樣做有利于減少使用者維護(hù)的難度也保證了Eureka的在運(yùn)行時(shí)的健壯性。而且Eureka就是為發(fā)現(xiàn)服務(wù)所設(shè)計(jì)的,它有獨(dú)立的客戶端程序庫(kù),同時(shí)提供心 跳服務(wù)、服務(wù)健康監(jiān)測(cè)、自動(dòng)發(fā)布服務(wù)與自動(dòng)刷新緩存的功能。但是,如果使用ZooKeeper你必須自己來(lái)實(shí)現(xiàn)這些功能。Eureka的所有庫(kù)都是開(kāi)源 的,所有人都能看到與使用這些源代碼,這比那些只有一兩個(gè)人能看或者維護(hù)的客戶端庫(kù)要好。
維護(hù)Eureka服務(wù)器也非常的簡(jiǎn)單,比如,切換一個(gè)節(jié)點(diǎn)只需要在現(xiàn)有EIP下移除一個(gè)現(xiàn)有的節(jié)點(diǎn)然后添加一個(gè)新的就行。Eureka提供了一個(gè) web-based的圖形化的運(yùn)維界面,在這個(gè)界面中可以查看Eureka所管理的注冊(cè)服務(wù)的運(yùn)行狀態(tài)信息:是否健康,運(yùn)行日志等。Eureka甚至提供 了Restful-API接口,方便第三方程序集成Eureka的功能。
結(jié)論
關(guān)于Service發(fā)現(xiàn)服務(wù)通過(guò)本文我們想說(shuō)明兩點(diǎn):1、留意服務(wù)運(yùn)行的硬件平臺(tái);2、時(shí)刻關(guān)注你要解決的問(wèn)題,然后決定 使用什么平臺(tái)。Knewton就是從這兩個(gè)方面考慮使用Eureka替換ZooKeeper來(lái)作為service發(fā)現(xiàn)服務(wù)的。云部署平臺(tái)是充滿不可靠性 的,Eureka可以應(yīng)對(duì)這些缺陷;同時(shí)Service發(fā)現(xiàn)服務(wù)必須同時(shí)具備高可靠性與高彈性,Eureke就是我們想要的!