文|石建偉(花名:卓與)螞蟻集團高階技術專家,專注服務領域中介軟體多年,負責螞蟻集團內部 Service Mesh 落地。
以下內容整理自 SOFAStack 四週年的分享
引言
繼 2019 年的 《螞蟻集團 Service Mesh 落地實踐與挑戰》之後,螞蟻集團在 Service Mesh 方向已經繼續探索演進近 3 年。這 3 年裡有哪些新的變化,以及對未來的思考是什麼,值此 SOFAStack 開源 4 週年之際,歡迎大家一起進入《螞蟻集團 Service Mesh 進展回顧與展望》章節探討交流。
本次交流將以如下次序展開:
螞蟻集團 Service Mesh 發展史
2018 年 3 月份|螞蟻集團的 Service Mesh 起步,MOSN 資料面誕生,起步就堅持走核心開源,內部能力走擴充套件的道路;
2019 年 6.18|我們在三大合併部署應用上接入了 MOSN,並且順利支撐了 6.18 大促;
2019 年雙 11 |螞蟻所有大促應用平穩的度過雙大促;
2020 年|MOSN 對內沉穩發展把接入應用覆蓋率提升至 90%,對外商業化開始嶄露頭角。螞蟻集團全站 90% 標準應用完成 Mesh 化接入。在商業版本中,SOFAStack“雙模微服務”架構也在江西農信、中信銀行等眾多大型金融機構成功落地實踐。
2021 年|隨著 Mesh 化的逐步成熟,多語言場景的逐步豐富,Mesh 化對中介軟體協議的直接支撐帶來擴充套件性問題也逐步凸顯,Dapr 的應用執行時概念也逐步崛起。這一年我們開源了 Layotto,期望通過應用執行時 API 的統一來解決應用和後端中介軟體具體實現耦合的問題,進一步解耦應用和基礎設施,最終解決應用在多雲執行時的廠商繫結問題。
2022 年|隨著 Mesh 化落地的基礎設施能力逐步完善,我們開始考慮 Mesh 化如何給業務帶來更多價值。在 Mesh 1.0 時代,我們儘可能下沉中介軟體相關的能力,提升了基礎設施的迭代效率;在 Mesh 2.0 時代,我們期望能有一種機制,可以讓業務側相對通用的能力做到按需下沉,並且具備一定的隔離性,避免下沉的能力影響 Mesh 資料代理主鏈路。
我們以圖示的方式簡述一下 Service Mesh 架構演進的幾個階段:
1、SOA 時代
中介軟體的客戶端,均直接整合在業務程式內:
2、Mesh 化階段
中介軟體能力下沉,應用和基礎設施實現部分解耦:
3、應用執行時階段
將應用和具體基礎設施的型別解耦,僅依賴標準 API 程式設計:
東西向流量規模化挑戰
Mesh 化後的資料面, MOSN 承載了應用間非常核心的東西向通訊鏈路,目前在螞蟻集團內部覆蓋應用數千,覆蓋容器數 10W+,海量的規模帶來了如長連線膨脹、服務發現資料量巨大、服務治理困難等問題。接下來,我們來聊一聊在演進的過程中遇到並解決掉的一些經典問題。
3.1 長連線膨脹問題
在海量規模的應用背後存在著複雜的呼叫關係,部分基礎性服務被大部分應用所依賴,由於呼叫方全連服務提供方的機制存在,一個基礎性服務的單 Pod 需要日常承載近 10W 長連線,單機 QPS 一般還是有上限的。我們以 1000 QPS 的 Pod 舉例,10w 長連線的場景下,每條長連線上的 QPS 是非常低的,假設所有連線的請求均等,平均每條長連線每 100s 僅有一次請求產生。
為了保證長連線的可用性,SOFA RPC 的通訊協議 Bolt 有定義心跳包,預設心跳包是 15s 一次,那麼一條長連線上的請求分佈,大概如下圖所示:
在上述場景中,一條長連線上心跳包的請求數量遠大於業務請求的數量,MOSN 在日常執行中,用於維護長連線可用控制程式碼持有記憶體的開銷,還有心跳包傳送的 CPU 開銷,在海量規模叢集下不可忽視。
基於以上問題,我們找到了兩個解法:
- 在保證連線可用的前提下減少心跳頻率;
- 在保證負載均衡的前提下降低應用間的連線數。
3.1.1 心跳退避
由於心跳的主要作用是儘可能早的發現長連線是否已不可用,通常我們認為經過 3 次心跳超時,即可判定一條長連線不可用。在一條長連線的生命週期裡,不可用的場景佔比是非常低的,如果我們把長連線的檢測週期拉長一倍就可以減少 50% 的心跳 CPU 損耗。
為了保障檢測的及時性,當出現心跳異常(如心跳超時等)場景時,再通過降低心跳週期來提高長連線不可用時的判定效率,基於以上思路我們設計了 MOSN 裡的長連線心跳退避策略:
- 當長連線上無業務請求且心跳正常響應時,逐步將心跳週期拉長 15s -> 90s。
- 當長連線上出現請求失敗或心跳超時的場景時,將心跳週期重置回 15s。
- 當長連線上存在正常業務請求時,降級本次心跳週期內的心跳請求。
通過以上心跳退避的手段,MOSN 的常態心跳 CPU 消耗降低至原來的 25%。
3.1.2 服務列表分片
從心跳退避的優化可以看出,在海量長連線的場景下,單長連線上的請求頻率是很低的,那麼維護這麼多長連線除了對負載均衡比較友好之外,其他的收益並不大。
那麼我們考慮另外一個優化方向,就是減少客戶端和服務端之間建立的長連線數量。
MOSN 使用一致性雜湊的策略對服務端機器進行分組:在客戶端的記憶體中,首先將全量的服務端機器列表加入到一致性雜湊環中,然後基於配置計算預期分片情況下的機器列表數 N,隨後根據客戶端機器 IP,從一致性雜湊環中獲取 N 個機器列表作為本機器的分片列表。
每個客戶端計算的雜湊環都是一樣的,不同的機器 IP 使得最終選擇的機器分片列表是不同的,實現了不同客戶端機器持有不同的服務端叢集分片的效果。
通過對服務列表的分片優化,客戶端向服務端建立的長連線數量急劇減小,在 6w 長連線且採用 50% 的負載均衡分片的場景下:
單機 CPU 降低約 0.4 Core,記憶體降低約 500M。
3.2 海量服務發現問題
MOSN 的服務發現能力沒有使用 Pilot,而是在內部直接和 SOFARegistry(服務註冊中心) 對接,使用這種架構的原因之一就是 RPC 的介面級服務發現,節點的 Pub、Sub 量巨大,海量應用的頻繁運維產生的節點變更推送對 Pilot 的效能和及時性挑戰都很大,社群有使用 Pilot 在稍大規模下做 CDS 下發的過程中也發現非常多的效能問題並提交 PR 解決,但對於螞蟻集團一個機房就有 200W Pub,2000W Sub 的規模下,Pilot 是完全無法承載的。
SOFARegistry 的架構是儲存和連線層分離,儲存為記憶體分片儲存,連線層也可以無限水平擴容,在內部海量節點變更下也能實現秒級變更推送。
雖然 SOFARegistry 的推送能力沒什麼問題,不過海量節點變更後產生的推送資料,會導致 MOSN 內有大量的 Cluster 重構,列表下發後到 Cluster 構建成功的過程中,會有大量的臨時記憶體產生,以及 CPU 計算消耗。這些尖刺型記憶體申請和 CPU 佔用,是可能直接影響請求代理鏈路穩定性的。
為了解決這個問題,我們也考慮過兩個優化方向:
- SOFARegistry 和 MOSN 之間把全量推送改造為增量推送;
- 服務發現模型從介面級切換為應用級。
其中第一點能帶來的效果是每次列表推送變化為原推送規模的 1/N,N 取決於應用變更時的分組數。
第二點能帶來的變化是更加明顯的,我們假設一個應用會發布 20 個介面,100 個應用的 Pod 產生的服務發現資料是 20*100=2000 條資料,介面粒度服務發現的資料總量會隨著應用介面數量的增長,數倍於應用節點數的規模持續增長;而應用級服務發現可以把節點總量控制在應用 Pod 數這個級別。
3.2.1 應用級服務發現演進
介面級服務發現示例(相同節點中多個服務中重複出現):
應用級服務發現示例(結構化表示應用、服務、地址列表間的關係):
通過對應用和介面關聯資訊的結構化改變,服務發現的節點數量可以下降一到兩個數量級。
介面級服務發現演進到應用級服務發現對於 RPC 框架來講是一個巨大的變化,社群中有 Dubbo 3.0 實現了應用級服務發現,但這種跨大版本的升級相容性考量很多,對於在奔跑的火車上換輪子這件事情,在框架層演進是比較困難的。由於螞蟻集團內部的 Service Mesh 已經覆蓋 90% 的標準應用,所以在服務發現演進方面我們可以做的更加激進,結合 MOSN + SOFARegistry 6.0,我們實現了介面級服務發現和應用級服務發現的相容性以及平滑切換的方案,通過 MOSN 版本的迭代升級,目前已經完成介面級到應用級服務發現的切換。
通過上述改進,生產叢集的服務發現資料 Pub 資料量下降 90%,Sub 資料量下降 80%,且整個過程對應用完全無感,這也是 Mesh 化業務和基礎設施解耦後帶來實際便利的體現。
3.2.2 MOSN Cluster 構造優化
通過應用級服務發現解決資料量變更過大的問題之後,我們還需要解決在列表變更場景下,產生的 CPU 消耗和臨時記憶體申請尖刺問題。
在這個問題中,通過對記憶體申請的分析,Registry Client 在收到服務端推送的列表資訊之後需要經歷反序列化,構造 MOSN 需要的 Cluster 模型並更新 Cluster 內容,其中比較重的就是構建 Cluster 過程中的 Subset 構建。通過使用物件池,並且儘量減少 byte[] 到 String 的拷貝,降低了記憶體分配,另外通過 Bitmap 優化 Subset 的實現,讓整個 Cluster 的構造更加高效且低記憶體申請。
經過上述優化,在超大叢集應用運維時,訂閱方列表變更臨時記憶體申請降低於原消耗的 30%,列表變更期間 CPU 使用量降低為原消耗的 24%。
3.3 服務治理智慧化演進
MOSN 把請求鏈路下沉之後,我們在服務治理方面做了非常多的嘗試,包括像客戶端精細化引流、單機壓測引流、業務鏈路隔離、應用級別的跨單元容災、單機故障剔除、各種限流能力等。由於篇幅關係,我這裡僅介紹下我們在限流場景下做的智慧化探索。
開源社群的 Sentinel 專案在限流方向做了一個非常好的實踐,MOSN 在做限流早期就和 Sentinel 團隊溝通,希望能基於 Sentinel 的 Golang 版本 SDK 來做擴充套件,站在巨人的肩膀上,我們做了更多的嘗試。
基於 Sentinel 可插拔的 Slot Chain 機制,我們在內部擴充套件了很多限流模組的實現,如自適應限流 Slot、叢集限流 Slot、熔斷 Slot、日誌統計 Slot 等。
在 MOSN 做限流能力之前,Java 程式內也是存在限流元件的。業務常用的是單機限流,一般會有一個精確的限流值,這個值需要經過反覆的壓測,才能得到單機的最大可健康承載的 TPS,並且會隨著業務應用本身的不斷迭代、功能增加,鏈路不斷複雜而逐步變化。所以每年大促前,都會準備多輪全鏈路壓測,來確保每個系統都能在滿足總 TPS 的情況下,對自身應用所應該配置的限流值有一個精確的預估。
為了解決限流配置難的問題,我們嘗試在 MOSN 內實現了自適應限流,根據對容器當前的介面併發、CPU、Load1 資訊採集上報,再結合最近幾個滑動視窗中,每個介面的請求量變化,可以自動識別是什麼介面的併發量增加導致了 CPU 資源佔用的提升。當負載超過一定的基線之後,限流元件可以自動識別出哪些介面應該被限流,以避免資源使用超過健康水位。
在實際的生產環境中,自適應限流可以迅速精準的定位異常來源,並秒級介入,迅速止血。同時也可以識別流量型別,優先降低壓測流量來讓生產流量儘可能成功。大促前再也不需要每個應用 Owner 去給自己應用的每個介面配置限流值,大幅度提升研發幸福感。
南北向流量打通
MOSN 作為 Service Mesh 的資料面主要在東西向流量上發力,除了東西向流量之外,還有南北向流量被多種閘道器分而治之。
螞蟻集團下有許多不同的公司主體,分別服務於不同的業務場景,各主體有與之對應的站點來部署應用對外提供服務。南北向流量最常見的是網際網路流量入口,這個角色在螞蟻集團由 Spanner 承載。除了網際網路流量入口之外,多個主體公司間也可能存在資訊互動,在同一個集團內的多公司主體如果資訊互動需要繞一道公網,穩定性會大打折扣,同時頻寬費用也會更貴。
為了解決跨主體的高效互通問題,我們通過 SOFAGW 搭建起了多主體間的橋樑,讓跨主體的應用間通訊和同主體內的 RPC 通訊一樣簡單,同時還具備鏈路加密、鑑權、可審計等能力,保障多主體間呼叫合規。
SOFAGW 基於 MOSN 2.0 架構打造,既能使用 Golang 做高效研發,同時也能享受 Envoy 在 Http2 等協議處理上帶來的超高效能。
簡單介紹一下 MOSN 2.0 架構,Envoy 提供了可擴充套件的 Filter 機制,來讓使用者可以在協議處理鏈路中插入自己的邏輯,MOSN 通過實現一層基於 CGO 的 Filter 擴充套件層,將 Envoy 的 Filter 機制進行了升級,我們可以用 Golang 來寫 Filter,然後嵌入 Envoy 被 CGO 的 Filter 呼叫。
SOFAGW 在 MOSN 2.0 之上構建了自己的閘道器代理模型,通過 SOFA 的 Golang 客戶端和控制面互動獲取配置資訊、服務發現資訊等,然後重組成 Envoy 的 Cluster 模型通過 Admin API 插入 Envoy 例項中。通過 Golang 的 Filter 擴充套件機制,SOFAGW 實現了螞蟻集團內部的 LDC 服務路由、壓測流量識別、限流、身份認證、流量複製、呼叫審計等能力。
由於 Envoy 的 Http2 協議處理效能相比純 Golang GRPC 實現高出 2~4倍,SOFAGW 選擇將 Triple (Http2 on GRPC)協議處理交給 Envoy 來處理,將 Bolt (SOFA RPC 私有協議)協議的處理依然交給 MOSN 來處理。
通過上述架構,SOFAGW 實現了螞蟻集團內部的全主體可信互通,在高效能和快速迭代開發間也取得了不錯的平衡。
應用執行時探索
隨著 Service Mesh 化的探索進入深水區,我們把很多能力沉澱到 Mesh 的資料面之後,也感受到每種協議直接下沉的便利性與侷限性。便利在於應用完全不用改造就可以平滑接入,侷限性是每種私有協議均需要獨立對接,且使用了 A 協議的應用,並不能直接在 B 協議上執行。
在多雲的環境下,我們希望可以做到讓應用 Write Once,Run on any Cloud!想要實現這一願景,我們需要將應用與基礎設施間進一步解耦,讓應用不直接感知底層的具體實現,而是使用分散式語義 API 來編寫程式。
這種思想在社群已經有 Dapr 作為先行者在探索:
(上圖來自 Dapr 官方文件)
Dapr 提供了分散式架構下的各種原子 API,如服務呼叫、狀態管理、釋出訂閱、可觀測、安全等,並且實現了不同分散式原語在不同雲上的對接實現元件。
(上圖來自 Dapr 官方文件)
Dapr 相當於是在 Service Mesh 之上提供給應用更加無侵入的分散式原語。2021 年中,我們基於 MOSN 開源了應用執行時 Layotto,Layotto 相當於是 Application Runtime 和 Service Mesh 的合集:
我們通過 Layotto 抽象出應用執行時 API ,將內部的 Service Mesh 演進至如下架構:
當然應用執行時是一個新的概念,如果這一層 API 抽象做不到足夠中立,那麼依然需要面臨使用方需要 N 選 1 的局面。所以我們也在和 Dapr 社群一起制定 Application Runtime API 的標準,組織 Dapr Sig API Group 用於推進 API 的標準化,也期望能有更多感興趣的同學一同加入。
期待未來大家的應用都可以 Write once, Run on any Cloud!
Mesh 2.0 探索
2022 年我們繼續向前探索,基於 MOSN 2.0 我們有了高效能的網路底座、易於擴充套件的 Mesh 資料面,基於 Layotto 我們有了無廠商繫結的應用執行時。
下一步我們期望基於 eBPF 實現 Mesh 資料面的進一步下沉,從 Pod 粒度下沉到 Node 粒度,同時服務於更多場景。如 Function、Serverless,另外基於 MOSN 2.0 的良好擴充套件能力,我們希望能進一步嘗試將業務應用相對通用的能力也可以沉澱下來,作為 Mesh 資料面的自定義外掛來為更多應用提供服務,幫助業務實現相對通用的業務能力也可以快速迭代升級。
相信在不遠的未來,Mesh 2.0 可以在螞蟻集團內部服務眾多通用場景,也能給社群帶來一些新的可能。
以上是本次分享的所有內容,希望大家能從對螞蟻集團 Service Mesh 的發展過程的交流中有所收穫。