天天看點

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

站在未來的路口,回望曆史的迷途,常常會很有意思,因為我們會不經意地興起瘋狂的念頭,例如如果當年某事提前發生了,而另外一件事又沒有發生會怎樣?一如當年的奧匈帝國皇位繼承人斐迪南大公夫婦如果沒有被塞爾維亞族熱血青年普林西普槍殺會怎樣,又如若當年的丘老道沒有經過牛家村會怎樣?

2008 年底,淘寶開啟一個叫做“五彩石”的内部重構項目,這個項目後來成為了淘寶服務化、面向分布式走自研之路,走出了網際網路中間件體系之始,而淘寶服務注冊中心 ConfigServer 于同年誕生。

2008 年前後,Yahoo 這個曾經的網際網路巨頭開始逐漸在公開場合宣講自己的大資料分布式協調産品 ZooKeeper,這個産品參考了 Google 發表的關于 Chubby 以及 Paxos 的論文。

2010 年 11 月,ZooKeeper 從 Apache Hadoop 的子項目發展為 Apache 的頂級項目,正式宣告 ZooKeeper 成為一個工業級的成熟穩定的産品。

2011 年,阿裡巴巴開源 Dubbo,為了更好開源,需要剝離與阿裡内部系統的關系,Dubbo 支援了開源的 ZooKeeper 作為其注冊中心,後來在國内,在業界諸君的努力實踐下,Dubbo + ZooKeeper 的典型的服務化方案成就了 ZooKeeper 作為注冊中心的聲名。

2015 年雙 11,ConfigServer 服務内部近 8 個年頭過去了,阿裡巴巴内部“服務規模”超幾百萬 ,以及推進“千裡之外”的 IDC 容災技術戰略等,共同促使阿裡巴巴内部開啟了 ConfigServer 2.0 到 ConfigServer 3.0 的架構更新之路。

時間走向 2018 年,站在 10 年的時間路口上,有多少人願意在追逐日新月異的新潮技術概念的時候,稍微慢一下腳步,仔細凝視一下服務發現這個領域,有多少人想到過或者思考過一個問題:

服務發現,ZooKeeper 真的是最佳選擇麼?

而回望曆史,我們也偶有迷思,在服務發現這個場景下,如果當年 ZooKeeper 的誕生之日比我們 HSF 的注冊中心 ConfigServer 早一點會怎樣?

我們會不會走向先使用 ZooKeeper 然後瘋狂改造與修補 ZooKeeper 以适應阿裡巴巴的服務化場景與需求的彎路?

但是,站在今天和前人的肩膀上,我們從未如今天這樣堅定的認知到,在服務發現領域,ZooKeeper 根本就不能算是最佳的選擇,一如這些年一直與我們同行的 Eureka 以及這篇文章 《Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery》那堅定的闡述一樣,為什麼你不應該用 ZooKeeper 做服務發現!

吾道不孤矣。

注冊中心需求分析及關鍵設計考量

接下來,讓我們回歸對服務發現的需求分析,結合阿裡巴巴在關鍵場景上的實踐,來一一分析,一起探讨為何說 ZooKeeper 并不是最合适的注冊中心解決方案。

注冊中心是 CP 還是 AP 系統?

CAP 和 BASE 理論相信讀者都已經耳熟能詳,其業已成了指導分布式系統及網際網路應用建構的關鍵原則之一,在此不再贅述其理論,我們直接進入對注冊中心的資料一緻性和可用性需求的分析:

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

資料一緻性需求分析

注冊中心最本質的功能可以看成是一個 Query 函數 

Si = F(service-name)

,以 

service-name

 為查詢參數,

service-name

 對應的服務的可用的 

endpoints (ip:port)

清單為傳回值.

注: 後文将 service 簡寫為 svc。

先來看看關鍵資料 

endpoints (ip:port)

 不一緻性帶來的影響,即 CAP 中的 C 不滿足帶來的後果 :

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

如上圖所示,如果一個 svcB 部署了 10 個節點 (副本 /Replica),如果對于同一個服務名 svcB, 調用者 svcA 的 2 個節點的 2 次查詢傳回了不一緻的資料,例如: S1 = { ip1,ip2,ip3...,ip9 }, S2 = { ip2,ip3,....ip10 }, 那麼這次不一緻帶來的影響是什麼?相信你一定已經看出來了,svcB 的各個節點流量會有一點不均衡。

ip1 和 ip10 相對其它 8 個節點{ip2...ip9},請求流量小了一點,但很明顯,在分布式系統中,即使是對等部署的服務,因為請求到達的時間,硬體的狀态,作業系統的排程,虛拟機的 GC 等,任何一個時間點,這些對等部署的節點狀态也不可能完全一緻,而流量不一緻的情況下,隻要注冊中心在 SLA 承諾的時間内(例如 1s 内)将資料收斂到一緻狀态(即滿足最終一緻),流量将很快趨于統計學意義上的一緻,是以注冊中心以最終一緻的模型設計在生産實踐中完全可以接受。

  • 分區容忍及可用性需求分析

接下來我們看一下網絡分區(Network Partition)情況下注冊中心不可用對服務調用産生的影響,即 CAP 中的 A 不滿足時帶來的影響。

考慮一個典型的 ZooKeeper 三機房容災 5 節點部署結構 (即 2-2-1 結構),如下圖:

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

當機房 3 出現網絡分區 (Network Partitioned) 的時候,即機房 3 在網絡上成了孤島,我們知道雖然整體 ZooKeeper 服務是可用的,但是節點 ZK5 是不可寫的,因為聯系不上 Leader。

也就是說,這時候機房 3 的應用服務 svcB 是不可以新部署,重新啟動,擴容或者縮容的,但是站在網絡和服務調用的角度看,機房 3 的 svcA 雖然無法調用機房 1 和機房 2 的 svcB, 但是與機房 3 的 svcB 之間的網絡明明是 OK 的啊,為什麼不讓我調用本機房的服務?

現在因為注冊中心自身為了保腦裂 (P) 下的資料一緻性(C)而放棄了可用性,導緻了同機房的服務之間出現了無法調用,這是絕對不允許的!可以說在實踐中,注冊中心不能因為自身的任何原因破壞服務之間本身的可連通性,這是注冊中心設計應該遵循的鐵律! 後面在注冊中心用戶端災容上我們還會繼續讨論。

同時我們再考慮一下這種情況下的資料不一緻性,如果機房 1,2,3 之間都成了孤島,那麼如果每個機房的 svcA 都隻拿到本機房的 svcB 的 ip 清單,也即在各機房 svcB 的 ip 清單資料完全不一緻,影響是什麼?

其實沒啥大影響,隻是這種情況下,全都變成了同機房調用,我們在設計注冊中心的時候,有時候甚至會主動利用這種注冊中心的資料可以不一緻性,來幫助應用主動做到同機房調用,進而優化服務調用鍊路 RT 的效果!

通過以上我們的闡述可以看到,在 CAP 的權衡中,注冊中心的可用性比資料強一緻性更寶貴,是以整體設計更應該偏向 AP,而非 CP,資料不一緻在可接受範圍,而 P 下舍棄 A 卻完全違反了注冊中心不能因為自身的任何原因破壞服務本身的可連通性的原則。

服務規模、容量、服務聯通性

你所在公司的“微服務”規模有多大?數百微服務?部署了上百個節點?那麼 3 年後呢?網際網路是産生奇迹的地方,也許你的“服務”一夜之間就家喻戶曉,流量倍增,規模翻番!

當資料中心服務規模超過一定數量 (服務規模 =F{服務 pub 數, 服務 sub 數}),作為注冊中心的 ZooKeeper 很快就會像下圖的驢子一樣不堪重負

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

其實當 ZooKeeper 用對地方時,即用在粗粒度分布式鎖,分布式協調場景下,ZooKeeper 能支援的 tps 和支撐的連接配接數是足夠用的,因為這些場景對于 ZooKeeper 的擴充性和容量訴求不是很強烈。

但在服務發現和健康監測場景下,随着服務規模的增大,無論是應用頻繁釋出時的服務注冊帶來的寫請求,還是刷毫秒級的服務健康狀态帶來的寫請求,還是恨不能整個資料中心的機器或者容器皆與注冊中心有長連接配接帶來的連接配接壓力上,ZooKeeper 很快就會力不從心,而 ZooKeeper 的寫并不是可擴充的,不可以通過加節點解決水準擴充性問題。

要想在 ZooKeeper 基礎上硬着頭皮解決服務規模的增長問題,一個實踐中可以考慮的方法是想辦法梳理業務,垂直劃分業務域,将其劃分到多個 ZooKeeper 注冊中心,但是作為提供通用服務的平台機構組,因自己提供的服務能力不足要業務按照技術的指揮棒配合劃分治理業務,真的可行麼?

而且這又違反了因為注冊中心自身的原因(能力不足)破壞了服務的可連通性,舉個簡單的例子,1 個搜尋業務,1 個地圖業務,1 個大文娛業務,1 個遊戲業務,他們之間的服務就應該老死不相往來麼?也許今天是肯定的,那麼明天呢,1 年後呢,10 年後呢?誰知道未來會要打通幾個業務域去做什麼奇葩的業務創新?注冊中心作為基礎服務,無法預料未來的時候當然不能妨礙業務服務對未來固有聯通性的需求。

注冊中心需要持久存儲和事務日志麼?

需要,也不需要。

我們知道 ZooKeeper 的 ZAB 協定對每一個寫請求,會在每個 ZooKeeper 節點上保持寫一個事務日志,同時再加上定期的将記憶體資料鏡像(Snapshot)到磁盤來保證資料的一緻性和持久性,以及當機之後的資料可恢複,這是非常好的特性,但是我們要問,在服務發現場景中,其最核心的資料 - 實時的健康的服務的位址清單真的需要資料持久化麼?

對于這份資料,答案是否定的。

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

如上圖所示,如果 svcB 經曆了注冊服務 (ip1) 到擴容到 2 個節點(ip1,ip2)到因當機縮容 (ip1 當機),這個過程中,産生了 3 次針對 ZooKeeper 的寫操作。

但是仔細分析,通過事務日志,持久化連續記錄這個變化過程其實意義不大,因為在服務發現中,服務調用發起方更關注的是其要調用的服務的實時的位址清單和實時健康狀态,每次發起調用時,并不關心要調用的服務的曆史服務位址清單、過去的健康狀态。

但是為什麼又說需要呢,因為一個完整的生産可用的注冊中心,除了服務的實時位址清單以及實時的健康狀态之外,還會存儲一些服務的中繼資料資訊,例如服務的版本,分組,所在的資料中心,權重,鑒權政策資訊,service label 等元資訊,這些資料需要持久化存儲,并且注冊中心應該提供對這些元資訊的檢索的能力。

Service Health Check

使用 ZooKeeper 作為服務注冊中心時,服務的健康檢測常利用 ZooKeeper 的 Session 活性 Track 機制 以及結合 Ephemeral ZNode 的機制,簡單而言,就是将服務的健康監測綁定在了 ZooKeeper 對于 Session 的健康監測上,或者說綁定在 TCP 長連結活性探測上了。

這在很多時候也會造成緻命的問題,ZK 與服務提供者機器之間的 TCP 長連結活性探測正常的時候,該服務就是健康的麼?答案當然是否定的!注冊中心應該提供更豐富的健康監測方案,服務的健康與否的邏輯應該開放給服務提供方自己定義,而不是一刀切搞成了 TCP 活性檢測!

健康檢測的一大基本設計原則就是盡可能真實的回報服務本身的真實健康狀态,否則一個不敢被服務調用者相信的健康狀态判定結果還不如沒有健康檢測。

注冊中心的容災考慮

前文提過,在實踐中,注冊中心不能因為自身的任何原因破壞服務之間本身的可連通性,那麼在可用性上,一個本質的問題,如果注冊中心(Registry)本身完全當機了,svcA 調用 svcB 鍊路應該受到影響麼?

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

是的,不應該受到影響。

服務調用(請求響應流)鍊路應該是弱依賴注冊中心,必須僅在服務釋出,機器上下線,服務擴縮容等必要時才依賴注冊中心。

這需要注冊中心仔細的設計自己提供的用戶端,用戶端中應該有針對注冊中心服務完全不可用時做容災的手段,例如設計用戶端緩存資料機制(我們稱之為 client snapshot)就是行之有效的手段。另外,注冊中心的 health check 機制也要仔細設計以便在這種情況不會出現諸如推空等情況的出現。

ZooKeeper 的原生用戶端并沒有這種能力,是以利用 ZooKeeper 實作注冊中心的時候我們一定要問自己,如果把 ZooKeeper 所有節點全幹掉,你生産上的所有服務調用鍊路能不受任何影響麼?而且應該定期就這一點做故障演練。

你有沒有 ZooKeeper 的專家可依靠?

ZooKeeper 看似很簡單的一個産品,但在生産上大規模使用并且用好,并不是那麼理所當然的事情。如果你決定在生産中引入 ZooKeeper,你最好做好随時向 ZooKeeper 技術專家尋求幫助的心理預期,最典型的表現是在兩個方面:

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

難以掌握的 Client/Session 狀态機

ZooKeeper 的原生用戶端絕對稱不上好用,Curator 會好一點,但其實也好的有限,要完全了解 ZooKeeper 用戶端與 Server 之間的互動協定也并不簡單,完全了解并掌握 ZooKeeper Client/Session 的狀态機(下圖)也并不是那麼簡單明了:

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

但基于 ZooKeeper 的服務發現方案卻是依賴 ZooKeeper 提供的長連接配接 /Session 管理,Ephemeral ZNode,Event&Notification, ping 機制上,是以要用好 ZooKeeper 做服務發現,恰恰要了解這些 ZooKeeper 核心的機制原理,這有時候會讓你陷入暴躁,我隻是想要個服務發現而已,怎麼要知道這麼多?而如果這些你都了解了并且不踩坑,恭喜你,你已經成為 ZooKeeper 的技術專家了。

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

難以承受的異常處理

我們在阿裡巴巴内部應用接入 ZooKeeper 時,有一個《ZooKeeper 應用接入必知必會》的 WIKI,其中關于異常處理有過如下的論述:

如果說要選出應用開發者在使用 ZooKeeper 的過程中,最需要了解清楚的事情?那麼根據我們之前的支援經驗,一定是異常處理。

當所有一切(主控端,磁盤,網絡等等)都很幸運的正常工作的時候,應用與 ZooKeeper 可能也會運作的很好,但不幸的是,我們整天會面對各種意外,而且這遵循墨菲定律,意料之外的壞事情總是在你最擔心的時候發生。

是以務必仔細了解 ZooKeeper 在一些場景下會出現的異常和錯誤,確定您正确的了解了這些異常和錯誤,以及知道您的應用如何正确的處理這些情況。

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

ConnectionLossException 和 Disconnected 事件

簡單來說,這是個可以在同一個 ZooKeeper Session 恢複的異常 (Recoverable), 但是應用開發者需要負責将應用恢複到正确的狀态。

發生這個異常的原因有很多,例如應用機器與 ZooKeeper 節點之間網絡閃斷,ZooKeeper 節點當機,服務端 Full GC 時間超長,甚至你的應用程序 Hang 死,應用程序 Full GC 時間超長之後恢複都有可能。

要了解這個異常,需要了解分布式應用中的一個典型的問題,如下圖:

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

在一個典型的用戶端請求、服務端響應中,當它們之間的長連接配接閃斷的時候,用戶端感覺到這個閃斷事件的時候,會處在一個比較尴尬的境地,那就是無法确定該事件發生時附近的那個請求到底處在什麼狀态,Server 端到底收到這個請求了麼?已經處理了麼?因為無法确定這一點,是以當用戶端重新連接配接上 Server 之後,這個請求是否應該重試(Retry)就也要打一個問号。

是以在處理連接配接斷開事件中,應用開發者必須清楚處于閃斷附近的那個請求是什麼(這常常難以判斷),該請求是否是幂等的,對于業務請求在 Server 端服務處理上對于"僅處理一次" "最多處理一次" "最少處理一次"語義要有選擇和預期。

舉個例子,如果應用在收到 ConnectionLossException 時,之前的請求是 Create 操作,那麼應用的 catch 到這個異常,應用一個可能的恢複邏輯就是,判斷之前請求建立的節點的是否已經存在了,如果存在就不要再建立了,否則就建立。

再比如,如果應用使用了 exists Watch 去監聽一個不存在的節點的建立的事件,那麼在 ConnectionLossException 的期間,有可能遇到的情況是,在這個閃斷期間,其它的用戶端程序可能已經建立了節點,并且又已經删除了,那麼對于目前應用來說,就 miss 了一次關心的節點的建立事件,這種 miss 對應用的影響是什麼?是可以忍受的還是不可接受?需要應用開發者自己根據業務語義去評估和處理。

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

SessionExpiredException 和 SessionExpired 事件

Session 逾時是一個不可恢複的異常,這是指應用 Catch 到這個異常的時候,應用不可能在同一個 Session 中恢複應用狀态,必須要重建立立新 Session,老 Session 關聯的臨時節點也可能已經失效,擁有的鎖可能已經失效。...

我們阿裡巴巴的小夥伴在自行嘗試使用 ZooKeeper 做服務發現的過程中,曾經在我們的内網技術論壇上總結過一篇自己踩坑的經驗分享

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

在該文中中肯的提到:

... 在編碼過程中發現很多可能存在的陷阱,毛估估,第一次使用 zk 來實作叢集管理的人應該有 80% 以上會掉坑,有些坑比較隐蔽,在網絡問題或者異常的場景時才會出現,可能很長一段時間才會暴露出來 ...

這篇文章已經分享到雲栖社群, 你可以點選 這裡 詳細閱讀。

向左走,向右走

阿裡巴巴是不是完全沒有使用 ZooKeeper?并不是!

熟悉阿裡巴巴技術體系的都知道,其實阿裡巴巴維護了目前國内乃至世界上最大規模的 ZooKeeper 叢集,整體規模有近千台的 ZooKeeper 服務節點。

同時阿裡巴巴中間件内部也維護了一個面向大規模生産的、高可用、更易監控和運維的 ZooKeeper 的代碼分支 TaoKeeper,如果以我們近 10 年在各個業務線和生産上使用 ZooKeeper 的實踐,給 ZooKeeper 用一個短語評價的話,那麼我們認為 ZooKeeper 應該是 “The King Of Coordination for Big Data”!

阿裡巴巴為什麼不用 ZooKeeper 做服務發現?

在粗粒度分布式鎖,分布式選主,主備高可用切換等不需要高 TPS 支援的場景下有不可替代的作用,而這些需求往往多集中在大資料、離線任務等相關的業務領域,因為大資料領域,講究分割資料集,并且大部分時間分任務多程序 / 線程并行處理這些資料集,但是總是有一些點上需要将這些任務和程序統一協調,這時候就是 ZooKeeper 發揮巨大作用的用武之地。

但是在交易場景交易鍊路上,在主業務資料存取,大規模服務發現、大規模健康監測等方面有天然的短闆,應該竭力避免在這些場景下引入 ZooKeeper,在阿裡巴巴的生産實踐中,應用對 ZooKeeper 申請使用的時候要進行嚴格的場景、容量、SLA 需求的評估。

是以可以使用 ZooKeeper,但是大資料請向左,而交易則向右,分布式協調向左,服務發現向右。

結語

感謝你耐心的閱讀到這裡,至此,我相信你已經了解,我們寫這篇文章并不是全盤否定 ZooKeeper,而隻是根據我們阿裡巴巴近 10 年來在大規模服務化上的生産實踐,對我們在服務發現和注冊中心設計及使用上的經驗教訓進行一個總結,希望對業界就如何更好的使用 ZooKeeper,如何更好的設計自己的服務注冊中心有所啟發和幫助。

最後,條條大路通羅馬,衷心祝願你的注冊中心直接就誕生在羅馬。

原文釋出時間為:2018-06-6

本文作者:坤宇

本文來自雲栖社群合作夥伴“

聊聊架構

”,了解相關資訊可以關注“

”。

繼續閱讀