Service Mesh 實踐指南:從單體應用到 Service Mesh 的曲折歷程
技術支撐著業務高歌猛進,業務增長反過來又驅動著技術不斷向前演化,這是每個網際網路公司發展過程中不變的旋律。作為全國最大社交媒體網站的微博更是如此。
從 2009 年上線至今,微博架構經歷了從最初的單體應用到後面的 RPC 服務化、容器化、混合雲架構以及現在的跨語言服務化和 Service Mesh 等諸多階段,架構演變支撐著微博業務的一次次華麗轉身,也見證了微博的飛速成長。
那麼,微博架構是如何從一開始的單體應用一步步成長為今天的龐大規模?作為國內最早落地 Service Mesh 的公司,微博為什麼要選擇做 Service Mesh,具體又是如何做的?這是本系列文章將試圖回答的問題。
在第一篇文章中,我會結合微博架構演進的歷程向你展示當前微博架構的整體概貌。從第二篇文章開始,我將聚焦於微博在 Service Mesh 方面的具體落地實踐,為你詳細講解微博自研的服務網格 WeiboMesh 從 0 到 1 的成長曆程。當然,其中也會有我自己對架構演進的一些思考。
下面,我們進入正題。
在業務發展的每個階段,面臨的問題都不盡相同,而問題又有各種優先順序,這就難免為了解決某些較為迫切的問題而引入一些當時不 Care 的問題,這也是日常架構演化過程中難以避免的魔咒。
魚和熊掌不可兼得,所以架構演化的真諦就在於各種方案評估中的利弊權衡,在以業務為重的前提下進行正向演化。
那麼,微博各個發展時期的架構又是如何演化的呢?
業務初期:單體架構
微博發展初期,使用者規模高速增長,伴隨而來的還有不斷湧現的新業務,因為你不知道哪個今天還名不見經傳的業務明天就會搖身一變成為備受矚目的核心業務,大家就像在白紙上瘋狂試錯。這個時候,快速開發上線才是當務之急。
為了達成這個目標,我們對整個系統做了優良的模組化設計,每個業務作為一個獨立的模組,保障業務能獨立開發、釋出並快速上線。
同時我們自研了容器框架 Cedrus,並在接入層實現了一些通用的邏輯,比如提供統一的認證、頻次控制、黑白名單、降級開關、配置服務等功能。平臺服務內部則通過 Jar 包的方式依賴呼叫,對外暴露 API 介面。
在部署方面,我們採用大服務池整體部署的方案,這樣一來我們可以更合理地利用資源,避免為每個專案每個模組單獨配置資源。
這種單體架構的好處在於資源利用更合理,通過 Jar 包應用來完成的本地服務呼叫不僅更直接,效能也更高,模組之間開發也相互獨立,很多通用的前置邏輯被統一剝離出來後,業務的同學只需要關注自己的邏輯實現就可以了。當然,單體架構的缺點也很突出,最主要的就是耦合。專案之間強耦合帶來了一系列問題,比如升級困難,迴歸測試非常難做,以及隨著業務模組的增多,模組之間的依賴解決困難等問題,此外還有各種 Jar 包衝突,越往後功能越加臃腫,業務變更十分吃力。這時候,就必須考慮做拆分了。
業務穩定期:服務化改造
要對當時規模已經十分龐大的微博平臺做拆分是一件極具挑戰的事情,好在當時微博業務的發展已進入穩定期。這就是我之前所說的,在每個發展階段我們所面臨的問題都不盡相同,如果說前期大規模的單體架構是為了解決當時業務的溫飽問題,那麼以系統拆分為出發點的服務化改造就是要做到不但要溫飽,還要吃得好。
我們希望通過架構改造來達到保證服務高可用的同時實現業務解耦的目的,以便更好地支撐業務發展。如何做到這一點?
我們主要從業務模組拆分方面來考慮,基於我們之前模組化的單體應用架構,按業務模組拆分是最自然也最容易想到的方案。我們只需要把以往基於 Jar 包依賴的大一統平臺按照業務模組做拆分然後獨立部署,即可達到業務解耦的目的。
RPC 服務化
但是拆分之後服務之間依賴的問題如何解決?我們當時面臨兩種選擇,一種是提供 HTTP 的 RESTful 介面,一種是使用 RPC 提供遠端過程呼叫。
RESTful 介面的好處在於 HTTP 是明文協議,開發除錯比較方便,RESTful 介面描述也足夠簡單。但缺點也很突出,HTTP 協議本身比較臃腫,我們內部服務的依賴主要解決資料可靠性傳輸的問題,並不需要那麼多無用的請求頭。
相比之下, RPC 具有可程式設計特性,可以根據微博的業務特性定製化開發。同時,因為使用私有協議,所以能大大減小每個請求的體量,這對內部服務動輒過億的依賴呼叫來說,能節省不少專線頻寬,同時能收穫更高的訪問效能。所以我們決定採用 RPC 的方式來解耦服務間依賴。
另外,在技術選型方面,因為 RPC 框架會是我們今後服務依賴的核心元件,所以我們特別慎重。選擇使用現成的開源方案還是走自研之路?這是當時擺在我們面前的一大現實問題。
我們最終決定自研 RPC 框架,原因在於,如果使用開源方案,很難找到一款完全適合微博場景的 RPC 框架,就算找到一個差不多能滿足的,但要在線上生產使用,不摸個一清二楚我們也不敢上,這個熟悉的過程成本同樣不低。
而且,開源軟體的發展一般遵從於社群意志,不以微博的需求為轉移。如果到時候出現不得不基於微博場景的分叉,離社群越來越遠,還不如一開始就走自研的道路。所以 2013 年起我們開始了 RPC 服務化改造之路。
我們自研了微博自己的 RPC 框架 Motan,結合註冊中心,實現了業務的解耦和服務的高效治理。之後, Motan 經歷了多次熱點事件和三節高峰的嚴峻考驗,穩定性和可靠性都得到了實際場景的驗證。
容器化、混合雲架構
然而好景不長,大量按業務拆分的服務獨立部署使得服務的擴縮容操作緩慢,直接拉低了峰值流量的應對能力。正好這個時候, Docker 提出的一整套圍繞容器部署和管理相關的生態系統逐漸完善,於是微博率先在重點業務上嘗試了容器化。與此同時,虛擬化、雲端計算領域也在飛速發展。為了低成本高效率地應對各種極端峰值,我們在容器化的基礎上探索了公有云和私有云混合部署的模式,研發了微博 DCP 混合雲平臺,實現了資源的動態擴縮容,結合 Motan RPC 的服務治理實現了對流量的彈性排程。
至此,微博平臺在 Java 技術棧形成了配套完善的一整套服務體系,有完善的服務治理相關元件、明確的 SLA 指標、完備的 Trace、監控等體系保障微博平臺的高效能高可用運轉。
跨語言服務化之路
但微博整體技術棧比較多樣化,異構系統一般通過 RESTful 介面進行互動。由於每個團隊的服務部署都不盡相同,依賴的服務訪問往往要經過層層轉發,此過程中繁重的網路 I/O 拖長了請求耗時,影響了系統性能同時也使得問題排查變得更復雜。另外,每種語言都有一套自己的系統或者指標,這也帶來了許多不必要的重複資源浪費。
如何解決跨語言互動,平衡各種語言間服務治理能力與標準各異的問題?如何對日常問題快速排查,使上下游業務更容易觀測和聯動?我們認為必須要有一套跨語言的服務治理方案來解決異構語言互動以及統一服務治理標準等問題。所以從 2016 年開始,我們開始探索跨語言服務化的道路。
Java 和 PHP 是微博內部使用最多的兩種語言,所以我們起初的跨語言是立足於微博平臺的 Java 體系,探索 Java 與 PHP 之間的跨語言呼叫。我們最初在 Motan RPC 實現了 PHP RPC 框架 Yar 的協議,實現了服務調通,但是這隻完成了 Java 和 PHP 之間的跨語言呼叫,而其他語言並沒有 Yar 協議。於是我們又調研了其他支援跨語言的 RPC 框架,發現 gRPC 可能與我們的需求更接近,於是我們希望通過在 Motan 中新增對 gRPC 協議的支援來達到跨語言的目的。
還是以 Java PHP 跨語言為起點,除了跨語言服務調通外,更為重要的是實現服務化的核心——服務治理功能。這時我們發現用 PHP 實現服務發現不太方便,因為通常 PHP 是以 PHP-FPM 的形式執行在前端伺服器,每個 FPM 程序相互獨立,並沒有一個統一常駐記憶體的地方來存取服務發現回來的結果以及每次服務請求的狀態等基本資訊。
我們還嘗試了本地守護程序和 OpenResty 的 Timer 來實現服務發現,但也只能實現最基礎的節點發現功能。而對於複雜的服務治理功能,比如需要基於每次請求完成情況而實現的請求雙發或者快速失敗等常用服務治理策略就比較吃力。
另外實現了基礎服務發現功能的 PHP 通過 gRPC 呼叫的效能也並沒有 gRPC 宣稱的那麼強悍。有時改造後的效果跟之前 RESTful 介面的訪問效能差不多。因為在微博場景下,比如取一個 Feed 列表,裡面每條微博的 proto 檔案就有百十個欄位,每次會請求回來大量資料, 而 PHP 在 PB 反序列化方面耗時非常大,這就直接抵消了 RPC 直連帶來的效能優化。
從跨語言服務化到 Service Mesh
拋開大 PB 反序列化帶來的效能損失,類似 PHP 這種原生沒有常駐記憶體控制能力的語言,實現服務治理都會面臨同樣的問題,就算能很自然地實現服務治理功能,難道需要每種語言都實現一套重複的服務治理功能嗎?顯然不是這樣的。所以我們就希望引入一個 Agent,來統一解決服務治理的問題,Client 只需要實現 Motan 協議解析,直接通過本機 Agent 呼叫遠端服務即可。這便是 Weibo Mesh 的雛形,也就是目前被大家所熟知的 SideCar 模式代理的 Service Mesh 實現。
那麼,我們是如何從跨語言服務化走到 Service Mesh 這條路的呢?要解答這個問題,只要弄清楚 Service Mesh 是什麼,搞清楚 Service Mesh 解決問題的邊界,答案就一目瞭然了。
Service Mesh 是什麼?這個詞最早是由開發 Linkerd 的 Buoyant 公司提出,Linkerd 的 CEO William 最早給出定義:服務網格(Service Mesh)是一個基礎設施層,功能在於處理服務間通訊,職責是負責實現請求的可靠傳遞。在實踐中,服務網格通常實現為輕量級網路代理,與應用程式部署在一起,但是對應用程式透明。
我們在解決跨語言服務化中服務間呼叫和統一服務治理所引入的 Agent 就是這個 Mesh 層。雖然 Service Mesh 是個新詞,但它描述的問題卻是一個固有問題,微服務發展到一定階段,當服務間的呼叫、依賴、服務治理複雜到一定程度後,都會面臨這個問題。所以 Service Mesh 是服務化必經之路,這就是為什麼我們的跨語言服務化最終會落腳到 WeiboMesh。
我們基於 Motan-go 實現了客戶端服務端的雙向代理,基於微博註冊中心 Vintage 實現了對 Agent 的動態指令控制,完成了傳輸與控制的重新定義,並結合 OpenDCP 平臺實現了動態流量排程和彈性擴縮容,保障了服務的高可用。目前已經有很多核心業務完成了基於 WeiboMesh 的升級改造,比如大家經常使用的微博熱搜、熱門微博等。我們從 2016 年開始起步,一路摸索,走到現在與 Service Mesh 理念完美契合,完成了 WeiboMesh 的主體建設。
接下來的幾篇文章我會就 WeiboMesh 的具體實現過程與探索中的經驗做一些總結和探討。
進一步學習 Service Mesh
-
針對 Service Mesh 的特點,落地過程中如何根據現有架構做出合理的取捨?
-
這個過程中有哪些容易掉入的陷阱?如何避免?
-
經過最近一年的發展,Service Mesh 形成了哪些事實規範?
-
經歷過整個過程之後,對於架構我有了哪些更深入的思考?