开启云原生 MOSN 新篇章 — 融合 Envoy 和 Golang 生态
注:本文是王發康(毅松)在 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——協議擴展篇
總結
以上是生活随笔為你收集整理的开启云原生 MOSN 新篇章 — 融合 Envoy 和 Golang 生态的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高并发场景下 disk io 引发的高时
- 下一篇: Go 超时引发大量 fin-wait2