作者:王發康(毅松)
來源:金融級分布式架構公衆号
注:本文是王發康(毅松)在 2021 GopherChina 上演講的文字稿,相關分享 PPT 可自行到 MOSN meetup下載下傳。
MOSN meetup 位址:
https://github.com/mosn/meetup MOSN 官方 Github 位址: https://github.com/mosn/mosn GitHub 位址: https://github.com/sofastack
前言
MOSN 在 Service Mesh 領域作為東西向服務治理網絡在螞蟻集團雙 11 、春節紅包等活動及開源社群都得到了一定實踐。為了能夠讓社群使用者更好的享受到這一技術紅利,MOSN 從 2018 年開源以來在社群開發者、使用者的共同努力下,使得 MOSN 在雲原生演進方面做了很多探索和實踐。比如 Istio 下另一個資料面 — MOSN、WebAssembly 在 MOSN 中的探索與實踐、MOSN 子項目 Layotto:開啟服務網格+應用運作時新篇章、MOSN 基于 Sentinel 的限流實踐、MOSN 中玩轉 Dubbo-go 等周邊生态展開合作。
Istio 下另一個資料面 — MOSN 文章連結: https://istio.io/latest/blog/2020/mosn-proxy MOSN 基于 Sentinel 的限流實踐文章連結: https://github.com/mosn/mosn/pull/1111 MOSN 中玩轉 Dubbo-go 等周邊生态展開合作文章連結: https://mosn.io/blog/posts/mosn-dubbo-integrate/
2021 年為了更好的為業務提效,MOSN 開啟了将雲原生進行到底的決心。本文介紹了 MOSN 在網絡擴充層的思考和技術選型,以及最終是如何通過使用 Envoy 作為 MOSN 的網絡層擴充,進而實作 MOSN 和 Envoy 生态打通。使得網絡層具備 C++ 高性能的同時,上層業務治理能力也能借助 GoLang 進行高效的定制化開發。

面臨的問題及挑戰
一、社群生态,如何最大化求同存異
最近幾年 Service Mesh 技術在雲原生社群也是百花齊放,雖然 MOSN 在開源社群也是備受開發者的關注,但是 Envoy 經過長久的發展其社群的活躍度和使用者量的積累這點是不可忽略的。另外選擇 MOSN 的使用者都是看重其二次開發的便捷性以及 GoLang 的生态豐富,選擇 Envoy 的使用者主要是看重其高性能的網絡處理能力及社群的活躍度。那我們是否能夠通過技術的手段将其二者優勢融為一體,發揮各自的特長,不要讓使用者顧此失彼。
二、單一的 Proxy,無法支撐其架構的演進
在 Proxy 層面,無論是 MOSN 還是 Envoy 都是在各自領域中發揮優勢。随着雲原生技術的快速發展和成熟以及業務的增長,既要 Proxy 能夠具備高研發效能,還要具備高處理性能,而單一的 Proxy 已經無法滿足目前業務架構上的持續演進。
- 東西向和南北向資料面 Proxy 逐漸統一:兩套資料面定位不同但功能上存在一定重疊,導緻維護成本高,未來需要逐漸收斂。這就要求 Proxy 不僅具備易擴充性友善業務方擴充東西向業務上的流量治理能力,而且還要具備抗高并發的能力滿足南北向高流量轉發。
- Service Mesh 部署形态逐漸向 Node 化架構演進:Service Mesh 規模化後,由于多出的 Proxy 勢必會導緻一定資源上的浪費,那在中心化和 Mesh 化之間做一次折中,即通過 Node 化部署形态來解決。Node 化後就要求 Proxy 能夠高效、穩定的承載多個 POD 的流量治理。
- Service Mesh 需要同時具備 Application Runtime 能力:雖然 Service Mesh 解決了微服務治理的痛點,但在實際業務開發中,緩存、資料庫、消息隊列、配置管理等,仍然需要維護一套重量級的 SDK 并且侵入應用代碼。目前業界的解決方案是在 Service Mesh 的基礎上多引入一個 Proxy 如 Dapr 來解決,這就導緻應用的 POD 需要維護多個容器,是以如何讓 Service Mesh 的 Proxy 具備快速複用 Dapr 能力成為解決該問題的關鍵。
我們的思考
針對上述問題分析過後,其實背後的原因是有共性的。比如将其統一為一個 sidecar,如果單純的從一個資料面改為另一個,那其中的改造成本是巨大的。那是否可以換個思路,為 MOSN 的網絡層增加可擴充性,即可以讓 MOSN 的網絡處理直接下沉至 Envoy,同時将這個能力剝離出來成為 Envoy 在 GoLang 上的一個标準能力,這樣就能夠讓 Envoy 和 MOSN 互相複用已有的能力。二者互相融合,各取所長,使其同時具備高研發效能和處理性能高,自然而然就解決上述“單一的 Proxy,無法支撐架構的演進”和“社群生态,如何最大化求同存異”所面臨的問題。互相融合後,不僅融合了各種的優勢,而且也能夠把兩邊的生态打通,借此 MOSN 社群和 Envoy 社群能形成雙赢的局面。
方案調研與分析
知道目前面臨問題的原因後,便有了一個宏觀的解決方向。于是就對此展開了相關調研,梳理了業界針對此問題的一些解決方案,綜合各種方案的優劣勢并結合螞蟻業務現狀以及開源社群使用者的痛點進行了分析和評估。
擴充方案調研
擴充方案評估
通過上述方案優劣勢的對比以及評估,MOE(MOSN on Envoy) 相比 ext-proc 無需跨程序 gRPC 通信,性能高,易管理;相比 Envoy WASM 擴充無需網絡 IO 操作轉換成本;相比 Lua 擴充生态好、能複用現有的 SDK,對于處理上層業務更合适。
同時我們将 Envoy 中增加 GoLang 擴充的這個方案也在 Envoy 社群進行了讨論,也得到了 Envoy 社群 Maintainer 的贊同。其中依賴的技術 CGO 是 GoLang 官方出品,該技術基本上在 GoLang 每個 release notes 中都有提到,說明也一直在維護的。另外業界也有很多項目在使用這項技術(比如:NanoVisor、Cilium、NginxUnit、Dragonboat、Badger、Go withOpenCV etc)其穩定性已經過一定的考驗了,同時我們自己也測試了 CGO 自身的開銷在 0.08 ~ 1.626 微秒,而且調用開銷也是屬于線性增長而非指數增長趨勢。
是以綜合穩定性、性能、改造成本以及社群生态等因素評估,MOE 解決方案無論在目前階段還是未來都具備一定優勢。
方案介紹
一、整體架構
如下是 MOE 的整體架構圖,最下面是各種高性能資料面,目前我們主要适配的是 Envoy。在資料面之上剝離了一層 GoLang L7 extension filter 的抽象,用于和底層的資料面連通;然後在 MOSN 側通過 GoLang L7 extension SDK 将 MOSN 連通;最後通過 CGO 這個通道将 Envoy 和 MOSN 打通。
整體架構如上圖所示,其核心包括如下三部分組成:
- GoLang L4/L7 extension filter
使用 C++ 實作的 Envoy 側的 GoLang L4/L7 filter ,該子產品通過 CGO API 來調用 GoLang 實作的 L4/L7 extension filter。
- GoLang L4/L7 extension SDK
GoLang L4/L7 extension SDK 會導出一些 CGO API,用于 Golang L4/L7 extension filter 和 L4/L7 extension GoLang filter 互動。
- L4/L7 extension filter via GoLang
L4/L7 extension filter 是 GoLang 語言開發的,用于對 Envoy 的請求或者響應做一些處理,最終是運作在 Envoy 工作線程之中。
二、功能職責
通過上面對整體架構的介紹,應該對 MOE 有了一個宏觀上的認識。接下來我們通過功能職責方面來介紹下 MOE 中 MOSN 和 Envoy 是如何各司其職的:整體思路就是充分發揮 GoLang 的高研發效能以及 Envoy 在網絡層的高性能特性,是以在 MOSN 側來擴充上層業務的服務治理能力,複用 Envoy 底層高效的 Eventloop 網絡模型。
MOSN 側做業務擴充:擴充非 xDS 服務發現、擴充 L4/L7 filter、擴充 Xprotocol 支援、Debug 及 Admin 管理、Metrics 監控統計;
Envoy 側複用基礎能力:複用高效 Eventloop 模型、複用 xDS 服務中繼資料通道、複用 L4/L7 filter、複用 Cluster LB、複用 State 統計。
三、工作流程
通過使用 GoLang 在 Envoy 中實作的“TraceID 事例”來介紹 Envoy GoLang extension 的工作流程:
1、請求/響應到達 Envoy 後,通過 GoLang L7 extension filter 将請求/響應資訊通過 API 封裝為特定格式;
2、然後将其封裝後的結構體通過 CGO API 傳遞給 GoLang extension framework;
3、該架構收到資料後将會執行 Trace ID filter(GoLang 實作的 Filter,用于生成一個 trace id 請求 header);
4、當 GoLang filter 執行完成後,會把處理後的資訊通過特定的結構體傳回給 GoLang L7 extension filter;
5、GoLang L7 extension filter 收到傳回的特定結構體資訊後,将其操作生效到目前請求/響應。
其中在上述的(1、2、4、5)步驟中涉及到 Envoy 和 GoLang 的互動協定、(2、4)中涉及到 Envoy 和 GoLang 之間的記憶體如何管理、(2)中阻塞操作處理,接下來是這些問題的解決方案:
- 互動協定(1、2、4、5)
将 Envoy 的請求使用 GoLang L7 extension filter 的 proxy_golang API(GoLang L7 extension SDK)進行封裝,然後通過 CGO 通知 GoLang 側的 filter manager 進行處理,其流程如下圖所示:
當 Envoy 側收到請求後,将請求的 header、body、trailer 封裝為 CGO_Request 類型,然後調用 proxy_golang_on_request,GoLang filter 處理後的結果會封裝為 CGO_Response 傳回給 Envoy 繼續處理。當 Envoy 側收到響應後,将響應的 header、body、trailer 封裝為 CGO_Resquest 類型,然後調用 proxy_golang_on_response,GoLang filter 處理後的結果會封裝為 CGO_Response 傳回給 Envoy 繼續處理。同時我們也期望可以和 Envoy、Cilium、WASM 社群合作共建這套 API 規範,這樣可以使得多個擴充方案底層依賴的 API 能夠标準化。
- 記憶體管理(2、4)
請求鍊路(CGO Request)
問題:将 Envoy 中的請求資訊如何高效的傳送給 GoLang,應該避免無效的記憶體拷貝操作。
方案:将 GoLang 中使用的 header、body 等資訊直接指向 Envoy 的請求 header、body 的指針,這樣請求從 Envoy 到達 GoLang 就不需要拷貝。
響應鍊路(CGO Response)
問題:請求在 GoLang 側執行完成後,将處理結果傳回給 Envoy,如果直接使用 GoLang 傳回的記憶體是不安全的,因為 GoLang 中的記憶體可能會被 GC。
方案:把 GoLang 中生成的 CGO_Response 對象儲到全局的 map 中,通過請求 id 進行映射,待 Envoy 使用完後,在将其對應的結構體從 map 中删除。
- 阻塞處理(2)
首先 Envoy 的事件模型是異步非阻塞的,如果 GoLang 實作的 HTTP filter 存在阻塞操作需要如何處理?
對于純計算(非阻塞)或請求鍊路中的旁路阻塞操作,按照正常流程執行即可。對于阻塞操作,通過 GoLang 的 goroutine(協程) 結合 Envoy 的 event loop callback 機制來解決:
1、當請求傳遞到 GoLang 側後,如果發現 GoLang 實作的 filter 中有阻塞操作;
2、則 GoLang 側會立刻啟動一個 goroutine 用來執行阻塞操作,同時會立刻傳回,并告知 Envoy 需要異步操作;
3、Envoy 收到該消息後,則會停止該請求上的 filter 處理,然後繼續處理其他的請求;
4、當 Golang 側的阻塞操作執行完成後,則會通過 dispatcher post 一個事件告訴 Envoy 繼續處理該請求。
除了上述我們在 GoLang 側自身程式出現同步導緻的阻塞外,是否還有其他場景?
我們都知道 GoLang 程式是 GMP 模型的,CGO 也是要遵守的,當 Envoy 通過 CGO 執行 MOSN(GoLang),此時 P 的數量如何管理?M 從哪來?
M 的問題比較好解決,由于宿主程式是 C 系列的,是以會把目前 Envoy 線程通過 GoLang runtime 的 needm 來僞裝成一個 M。其次就是 P 這個資源比較關鍵,如果此時沒有空閑的 P 此時就會卡主 CGO 執行,雖然目前我們生産上還未出現該問題,但是還是有這個可能性的。是以目前我們想到的解決方案就是為 Envoy 每個 worker thread 都預留對應的 P,保證每次 CGO 的時候都可以找到 P 資源。
服務相關中繼資料如何管理
MOSN 和 Envoy 的相關服務中繼資料資訊,是如何互動管理的?通過擴充 Envoy 中的 Admin API 使其支援 xDS 同等功能的 API, MOSN 內建的 Service Discovery 元件通過該 API(rest http) 和 Envoy 互動。使其 MOE 的服務發現能力也具備“雙模”能力,可同時滿足大規模及雲原生的服務發現通道。
如何 Debug
MOSN 和 Envoy 互相融合後,在運作時變為一個程序了,那之前的可觀測性以及調試如何保障?關于可觀測性方面這塊可以直接複用 MOSN 和 Envoy 自帶 admin API 及 metrics 功能,關于兩者之間的互動層我們增加了每次互動的耗時以及異常下的容災保護。最後就是關于一個程式即有 C++ 又有 GoLang 如何便捷性調試的問題,對此我們調研了相關方案,通過 net/rpc 網絡庫模拟 CGO 的調用,使得使用者調試 GoLang 側和之前 Native GoLang 一樣的方式調試即可。
方案總結
MOE 借助 CGO 通道不僅将 Envoy 和 MOSN 二者優勢融為一體,而且将 GoLang 生态內建進 Envoy 變成了可能。在研發效能方面通過将 MOSN 作為 Envoy 的動态庫,上層業務改動隻需編譯 GoLang 代碼即可,極大的提升了編譯速度,同時也增強了 Envoy 的自身擴充能力,使其能夠友善複用 MOSN 中現有的服務治理能力。性能方面,MOE 複用 Envoy 的高效網絡通道,之間的資料拷貝實作了 Zero Copy 可為 Dapr、Layotto 等提供高效網絡通道,同時 Envoy 使用 C++/C 系可友善的內建硬體加速能力。關于在服務中繼資料通道方面,MOE 即可複用 Envoy 原生的 xDS 又可以友善的內建 GoLang Discovery SDK 實作多種服務中繼資料通道支援。最後 MOE 不僅單純的将兩個軟體的優勢做了加法,更重要的是使得 MOSN/GoLang 可以和 Envoy 生态拉通,實作多社群技術共享。
開源共建及展望
秉着借力開源,反哺開源的思路。當我們對 MOE 方案 POC 驗證可行性後,我們也将這個思路在 MOSN 和 Envoy 社群展開了相關的 A proposal of high-performance L7 network GoLang extension for Envoy 讨論。在得到 Envoy maintainer 的認可後,我們也在主導關于使用 GoLang 來擴充 Envoy 的提案:Envoy's GoLang extension proposal ,歡迎大家參與進來讨論。
另外,近期我們也在設計 MOE 具備 L4 的 GoLang 擴充能力,這樣可友善使得 Envoy 內建 Layotto 或 Dapr 能力,進而同一個 sidecar 可具備 Service Mesh 與 Application runtime 能力,解決一個應用部署多個 Sidecar 導緻的運維複雜性問題。
方案實施效果
踩坑記錄
前面我們對 MOE 從整體架構、功能職責以及工作流程做了詳細的介紹,看似整體流程是可以跑通了。但是在我們落地實踐的過程中也是遇到了不少問題。
下面這個列子就是在 MOE 工程中剝離出來的一個問題的最小複現場景,當時我們在 CGO 傳遞資料的過程中,為了友善我們直接在 C++ 側通過指針來儲存 header 的長度,當其傳遞到 GoLang 側的時候,運作着運作着就 panic 掉了,如下所示:
通過分析後,發現是由于 GoLang 的函數棧在運作時觸發了棧擴容操作,進而會觸發 GoLang runtime 對目前函數棧上的指針做了一系列操作。對上述問題有影響的操作包括兩個:其一是會對目前棧上的指針指向的位址做判斷,檢查是否是一個安全的位址;其二是判斷目前棧上的指針指向的位址是否位于即将要擴容或者縮容的函數棧空間範圍内,如果在該範圍,則會根據擴縮容函數棧後的偏移量直接修改指針指向的位址。然而在我們的場景中,直接使用指針來存儲長度,這将導緻 runtime 中的第一個操作檢查不通過,直接抛異常。雖然當時我們通過調整 GoLang runtime invalidPtr 來繞過第一個檢查報錯,但一旦我們所記錄的長度正好和函數棧位址空間有交集的話,那對應長度的值也是會被修改的,是以也是不能滿足的。最後通過修改了長度的傳遞存儲方式來解決該問題。
由于篇幅有限,本文就不對所有問題進行展開了,整體來說遇到的問題還是非常有趣的,對此感興趣或有疑問的歡迎找我們交流。
最佳實踐
從 2021 年 1 月到 3 月期間我們進行了 MOE 的方案設計到編碼實作,并在 2 月份在 gRPC 網關叢集進行了小規模試點,各項名額都正常,MOE 雛形方案得到了線上真實流量的驗證。于是 3 月份的時候我們就找到兄弟團隊相關同學讨論:今年有一個目标需要全站資源成本優化,我們基礎設施是不是可以做點什麼?是以就順利成章的引出了 MOE 合作契機,經過幾輪讨論以及簡單的 POC 驗證後決定在經濟體互通網關場景,将内部的一個 Native Go 實作的網關基于 MOE 架構來替代螞蟻側的互通網關。其部署架構如下圖所示(目前互通網關螞蟻側已經在灰階中):
雙方目标達成一緻後,就準備開幹了。雖然 MOE 雛形之前已經踩過一些坑了,但是在實際适配的過程中還是多多少少遇到了一些問題,那最終我們也趕在了 6.18 這個時間點前進行了灰階上線。如下引自第一個基于 MOE 架構來替換網關的實踐 —《融合 Envoy 和 Go 語言生态打造高性能網關》,後續會有相關文章,敬請關注。
經過這樣一番“折騰”,最終新版本基于 MOE 的網關在 6.18 活動前上線,并承擔了總體流量的 10%,整體表現平穩,符合預期,現在逐漸擴大引流中(截止 7 月 2 号引進引流到 30% 左右)。從我們的壓測資料上看,相較于老版本,CPU 使用率和請求 RT 都有顯著下降,在TCP 連接配接數越高的場景,優勢越明顯,總體上能夠取得 2.6~4.3 倍的 QPS 性能提升。
總結
MOE 技術方案使得 MOSN/GoLang 和 Envoy 互相融合成為了可能,打破了 MOSN/GoLang 和 Envoy 割裂的社群生态現狀,為将來 Proxy 的持續演進奠定了堅實的技術基礎。随着在經濟體互通螞蟻側網關的落地實踐,其 MOE 技術方案的穩定性、可靠性、易用性等方面都得到了一定的考驗。同時秉着對技術的高标準要求以及能夠更好的服務好我們的使用者,我們也會持續探索和優化 MOE,如果該方案能也夠解決你業務上遇到的問題,歡迎聯系我們,最後感謝正在關注此項技術的使用者,歡迎和我們交流和讨論!MOSN 使用者交流群釘釘群号:33547952。
推薦閱讀
- MOSN 多協定擴充開發實踐
- MOSN 子項目 Layotto:開啟服務網格+應用運作時新篇
- 金融級能力成核心競争力,服務網格驅動企業創新
- Protocol Extension Base On Wasm——協定擴充篇