Proxyless Service Mesh 在百度的實踐與思考
Service Mesh 已經在雲原生界火了很多年,大家的探索熱情依然不減。而最近一段時間 Proxyless Service Mesh 也開始進入大家的視野,比如:“Istio 官宣支持 gRPC Proxyless Service Mesh”,“Dubbo 3.0 引入 Proxyless Service Mesh 架構”。
那麼,什麼是 Proxyless Service Mesh?它和原來的 Proxy Service Mesh 有什麼區別和優缺點?落地場景又有哪些呢?本文將結合 Proxyless Service Mesh 在百度的落地實踐,帶你一探究竟。
什麼是 Proxyless Service Mesh
先來看下 Proxy Service Mesh,也就是最常見的 Service Mesh 架構,一般如下所示:
-
每個 App 的 Pod 裏面,有一個獨立的 Sidecar 進程,App 之間的通信都通過 Sidecar 進程轉發。
-
有一個全局的控制平面(最常見的實現是 Istio),下發配置到每個 Sidecar,控制具體請求的轉發策略。
而 Proxyless Service Mesh 則是如下的架構:
-
由聯編到 App 進程的 rpc 框架負責服務之間的通信。
-
控制平面下發配置到每個 rpc 框架,rpc 框架按照配置進行具體請求的轉發(以上架構圖是經過簡化的,以目前主流的 Proxyless 實現,比如 gRPC 和 Istio 之間的通信是由 Istio Agent 來代理的,但這不影響後面的討論)。
Proxyless Service Mesh 的優缺點
如果簡單對比上述架構,不難得出 Proxyless 和 Proxy 模式的優缺點:
以上僅是一些直觀的分析,但當真正落地 Proxyless Service Mesh 的時候,會發現情況並不是我們想的那麼簡單。
百度的 Proxyless Service Mesh 實踐
Proxyless 第一階段
百度從 2018 年開始引入 Service Mesh,一開始是 Proxy 模式。到了 2020 年,我們在落地一些業務線的時候,發現 Proxy 模式很難在整個業務線全面鋪開:
-
業務其實能夠接受 Proxy 帶來的額外資源開銷,畢竟我們已經做了很多優化,比如將社區 Both Side 模式改成 Client Side 模式(即一次請求只過 Client 端的代理,不經過 Server 端的代理);比如將 Envoy 的流量轉發內核替換成 bRPC。我們能做到 Sidecar 佔業務進程的 cpu 消耗在 5% 以內,有的業務甚至不到 1%。
-
但業務無法接受 Proxy 帶來的延遲增長,即使我們已經把 Proxy 單次轉發增加的延遲優化到 0.2 毫秒以內,但由於整個業務系統包含了很大的一個調用拓撲,每條邊上增加一點點的延遲就能導致流量入口模塊增加較大的延遲,進而對業務 KPI 造成影響。
因此我們開始引入如下的 Proxyless Service Mesh 模式:
-
Envoy 從 Istio 拿到流量轉發配置,並轉化成 bRPC 能識別的配置
-
bRPC 通過 http 接口從 Envoy 中拿到流量轉發配置,並且按照該配置去調用其它服務
這種方式的好處是:
-
業務接入 Mesh,不會帶來延遲增長,也不會增加明顯的資源開銷(這裏的 Envoy 僅處理配置,資源開銷極小)。
-
業務可以享受 Mesh 的便利性,如集中管理配置、動態下發配置生效, 不再需要改代碼或改配置、上線、重啓生效,極大提升了服務治理的效率。
Proxyless 第二階段
但是,第一階段方案,存在一些明顯的問題:
-
bRPC 裏支持的服務治理策略較少,僅支持 Istio 中的少量功能,這就意味着大部分 Istio 的能力無法通過 Proxyless 模式享受到。
-
隨着業務不斷增加的需求,我們勢必要在 Service Mesh 中增加更多的服務治理能力。對於 Proxy 模式,我們需要在 Envoy 中開發,而對於 Proxyless 模式,我們又需要在 bRPC 中開發,由於 Envoy 和 bRPC 的策略架構差異很大,這些代碼很難複用,就意味着每一個需求都得重複開發兩遍。
因此,到了 2021 年,我們設計並實現了一套 Proxyless/Proxy 統一架構的 Service Mesh:
-
左邊是 Proxyless 模式,Envoy 負責把 xds 配置轉換成 bRPC 能識別的配置,bRPC 聯編到業務進程,從 Envoy 獲取配置並按配置轉發請求。
-
右邊是 Proxy 模式,Envoy 仍負責把 xds 配置轉換成 bRPC 能識別的配置,bRPC 聯編到 Envoy 進程,從 Envoy 獲取配置並按配置轉發請求。
無論是哪種模式,Envoy 都負責轉換配置,bRPC 負責配置執行,因此,所有的代碼都可以在 Proxy 和 Proxyless 模式下複用。
從業務場景上:
-
C++ 服務,可以聯編 bRPC,使用 Proxyless 模式;
-
延遲不敏感的非 C++ 服務(如 Go/Python/PHP/Java 等),可以無侵入使用 Proxy 模式;
-
延遲敏感的非 C++ 服務:什麼,既然延遲敏感了,爲啥不用 C++ 開發?(這裏的延遲敏感指的是一毫秒必爭的這種敏感程度)
服務治理能力提升:
-
在這個架構的基礎上,我們豐富了 bRPC 的服務治理能力,基本覆蓋了我們用到的所有 Istio 的能力,如權重路由、基於請求內容的路由、實例子集路由、流量複製、錯誤注入、異常實例驅逐、自定義錯誤碼重試等。
-
由於採用了 bRPC 架構,我們可以將先前公司內基於 bRPC 架構實現的優秀服務治理策略都集成到 Service Mesh,包括延遲感知的負載均衡、基於錯誤碼的動態實例調權、基於請求優先級的分級調度、基於分位值的動態超時和 Backup request、重試比例熔斷,這些能力是原生 Envoy 所缺失的,但是對於降低延遲、提升服務穩定性至關重要。
-
得益於 bRPC 多協議架構,上述所有的 Istio 服務治理能力、公司內部優秀服務治理策略,都可以支持 bRPC 中的所有協議。相比之下,在 Envoy 中只有 HTTP 協議是一等公民,其它協議的治理能力都相當薄弱。另外由於 bRPC 支持協議自動嗅探,我們無須擴展 Istio 下發協議類型,所有協議都按 HTTP 方式配置即可。
Proxyless 第三階段
-
bRPC 直接支持和 istio 通信,獲取 xds 配置並進行轉換;
-
Proxyless 模式,bRPC 聯編到業務進程中進行請求轉發;
-
Proxy 模式,bRPC 作爲一個獨立的 sidecar 進程進行請求轉發。
這個架構徹底解決了 Envoy 這個歷史包袱,讓 Service Mesh 輕裝上陣。
讓我們回過頭看一下前面說的 Proxyless 模式的兩個缺點是怎麼被解決的:
-
開發效率:我們仍然只需要開發一套策略,適用於所有語言(C++ 用 Proxyless,其它語言用 Proxy),不需要爲每個語言開發 SDK。
-
升級效率:由於百度內部 C++ 有 depend on stable 機制(類似於 Google 的 depend on HEAD),基礎庫有一個 stable 發佈分支,而其它模塊都依賴於基礎庫的 stable 分支。這樣,當我們升級了 bRPC 中的 Service Mesh 功能時,只要將代碼合併到 stable 分支,所有的上游模塊都會自動更新, 不需要再一個一個推動業務升級。至於非 C++ 語言,使用 Proxy 方式來保證升級效率。
似乎這兩個缺點在我們的場景裏都沒有了:)
Proxyless 模式的真正優勢
Proxyless 模式的真正優勢是性能嗎?一開始我們都是這麼認爲的,但是隨着我們落地 Proxyless 模式的過程,我們才逐漸發現 Proxyless 模式的更多優勢。讓我們來看看一些場景吧:
請求級別控制參數的場景
比如我們要在 Service Mesh 中實現一致性哈希負載均衡,原來沒有 Service Mesh 的時候,用戶通過 RPC 框架提供的一個 set_request_code() 方法來設置請求級別哈希碼,RPC 框架可以確保同一個 request_code 的請求被調度到同一個後端實例。
在 Service Mesh 中如何實現這個需求呢?如果是 Proxy 模式,負載均衡是在 Sidecar 做的,Sidecar 需要獲取到這個 request_code。總不能讓用戶改代碼吧?那麼只能修改 RPC 框架,讓框架把 request_code 傳給 Sidecar。如圖所示:
怎麼傳給 Sidecar 呢?得在協議裏的某個字段中傳過去,比如 HTTP 協議可以在 header 中傳過去,那麼其它協議呢?每個協議都得找一個地方來傳這個字段,每個協議都得實現一遍這個邏輯。
所以在 Proxy 模式下,傳參這個事的實現成本 =(RPC 框架發送參數 +Sidecar 接收參數)* 協議數量。
那麼在 Proxyless 模式下呢?這個參數本來 RPC 框架就能拿到,所以傳參的實現成本 =0。
除了一致性哈希,類似的場景還有很多,比如請求級別超時控制、請求級別路由參數等,在這些場景下,Proxyless 模式完勝。
框架回調業務代碼的場景
比如用戶想實現一個自定義重試策略,RPC 框架在訪問一次後端服務之後,調用用戶代碼來判斷是否需要重試。一個典型的流程如下所求:
現在業務想要接入 Service Mesh,如果用 Proxy 模式,該如何實現用戶自定義重試策略呢?考慮以下方案:
-
方案一:將業務代碼中的自定義重試策略實現到 Sidecar 代碼裏。問題是,業務的自定義重試策略可能不具有通用性,放在 Sidecar 這種通用基礎設施的代碼裏顯然不合適。
-
方案二:Sidecar 提供一種擴展機制,比如 WASM,用戶將自定義重試策略實現爲 WASM,以配置方式下發給 Sidecar 去執行。問題是,將原有代碼改造成 WASM 的成本可能較高,這會明顯降低業務接入 Mesh 的意願。而且 WASM 的性能也不高。
-
方案三:業務進程暴露一個服務接口,Sidecar 調用這個接口來決定是否要重試。問題是,這樣增加了業務進程和 Sidecar 之間的交互次數,增加了延遲,也增加了問題出現的概率。
在 Proxy 模式下,無論採用哪種方案,問題都很大。在 Proxyless 模式下呢?由於該功能 RPC 框架本來就支持,成本 =0。除了自定義重試策略,類似的場景還很多,比如自定義負載均衡策略、自定義 NamingService 等,在這些場景下,Proxyless 模式完勝。
動態多分片的場景
先解釋一下什麼叫動態多分片。多分片在搜索、推薦類業務是一個典型場景,也就是說一個服務的一個實例並不加載全量的數據,只加載一個分片的數據。客戶端調用此類服務時,需要將請求同時發送給不同分片的後端服務實例,獲取到每個分片的結果再進行彙總處理。動態多分片是指多個分片的服務組成一個分組,每次請求可以動態地選擇一組分片。之所以需要多個分組,一種場景是因爲數據發生擴容時,分片數會發生變化,爲了保證流量平滑遷移,會同時存在不同分片數量的分組;另一種場景是由於業務需要,不同的分組加載了不同業務屬性的數據,需要根據請求來動態確定調用哪個分組的服務。如下圖所示,分組 A 有 2 個分片,分組 B 有 3 個分片,而 k1、k2 則代表了不同的業務屬性。
在動態多分片場景下,業務代碼和 RPC 框架的一個簡化的交互流程如下:
之所以需要分兩個階段調用 RPC 框架,是因爲業務需要根據最終選定的分組和分片數量來拼裝業務的請求。
現在業務想要接入 Service Mesh,讓我們看看在 Proxy 模式下支持動態多分片的方案:
-
方案一:...(此處省略 300 字),不行,這個方案性能太差;
-
方案二:...(此處省略 500 字),不行,這個方案成本太高;
-
方案三:抱歉,我想不出其它方案了……
現在,讓我們看看 Proxyless 模式下支持動態多分片的方案:
-
沿用原來的二階段流程,在第一階段,執行 Service Mesh 中的各種路由策略,確定最終的分組,以及決策是否做流量複製、流量複製目的分組。
-
第二階段,先按原來 RPC 框架邏輯進行多分片並行調用,具體到單個分片上,使用 Service Mesh 中的負載均衡、超時重試等策略。如需流量複製,則異步執行流量複製到另一個分組。
雖然這個方案也有一定複雜性,但實現成本也不是很高,比 Proxy 模式的實現成本低多了。所以,在動態多分片場景,Proxyless 模式完勝。
服務可觀測場景
服務可觀測是 Service Mesh 的重點場景,我們來看一下這個場景下的一些需求吧:
-
用戶想實現分佈式 trace,需要將服務的入口流量和出口流量建立關聯,比如在處理 trace_id=x, span_id=y 的入口流量過程中,發出的出口流量,其 trace_id=x, parent_span_id=y。Sidecar: 這事我幹不了,得靠 RPC 框架。
-
用戶想把業務日誌和 RPC 日誌進行串聯,比如在處理 trace_id=x 的請求過程中,打印的業務日誌中都包含 trace_id=x 字段,這樣可以進行匯聚計算。Sidecar:這事我幹不了,得靠 RPC 框架和日誌庫打通。
-
用戶想監控請求處理過程的一些細化耗時,比如排隊時間、序列化反序列化時間。Sidecar:這事我幹不了,得靠 RPC 框架。
-
用戶想實現流量染色,比如將入口請求打上 k=v 標籤,然後由該請求觸發的整個調用鏈的請求都會帶上 k=v 標籤。Sidecar:這事我幹不了,得靠 RPC 框架。
-
用戶想針對業務代碼的耗時、cpu 使用進行分析,找出瓶頸。Sidecar:這事我幹不了……
爲什麼會出現這些情況呢?實現服務可觀測的最好方法就是深入服務內部,對於 Sidecar 來說,服務就是個黑盒,當然不好實現了。
所以,對於服務可觀測場景,Proxyless 模式完勝。
重新思考 Service Mesh 的本質
Service Mesh 的本質是 Sidecar 嗎?
誠然,Sidecar 是 Service Mesh 的一大賣點,比如方便支持多語言、和應用解耦,這可以讓用戶更方便地接入 Service Mesh。但是我們更應該關注 Service Mesh 給用戶帶來了什麼價值,如果用戶用了 Service Mesh 沒有收益,即使接入成本爲 0,用戶也會不接入的。
從用戶角度看,Service Mesh 帶來的是以下價值:
-
服務可用性提升:比如各種超時重試、限流熔斷策略帶來的提升;
-
延遲降低:比如延遲感知的負載均衡策略可以實現服務整體延遲的降低;
-
服務可觀測:比如鏈路黃金指標、調用鏈追蹤能力可以提升服務可觀測性;
-
流量調度靈活性:比如各種路由策略可以實現灰度發佈、跨機房切流等;
-
安全性:比如 TLS、各種認證鑑權機制可以提升服務間調用的安全性;
-
管理便利性:上述策略都可以通過控制平面集中管理,動態下發生效。
因此我認爲,Service Mesh 就是能讓服務間通信更可靠、更快、更透明、更靈活、更安全、更便於管理的基礎設施。
至於這個基礎設施是 Proxyless 還是 Proxy 模式,其實不是很重要。從實際業務場景出發,哪種模式更容易滿足業務需求,就用哪種方案。兩種模式各有自己的優勢場景,結合起來可以提供更好的服務。
Service Mesh 的本質是配置中心嗎?
有一種觀點認爲,Proxyless 模式不就是 RPC 框架 + 配置中心嗎?Proxy 模式不就是一個 7 層代理 + 配置中心嗎?這玩意很多年前就有了。
但是,Service Mesh 的控制平面,不能簡單看作配置中心。配置中心僅僅是簡單地將配置文件或者配置項進行下發,並不感知配置的實際含義。
而以 Istio 爲代表的控制平面,實際上是定義了 Service Mesh 的能力標準,比如各種路由、負載均衡策略等。如果只是做了一個簡單的 RPC 框架,暴露了幾個超時參數到配置中心來控制,那不叫 Service Mesh,因爲沒有實現 Service Mesh 的標準能力。即使 RPC 框架把 Service Mesh 的標準能力都實現了,但是沒有統一的協議和配置格式,不同的框架的配置方式五花八門,通信協議互相割裂,那也不能算 Service Mesh。
所以說,Service Mesh 是一組服務間通信的能力標準,實現了這些標準,就可以稱之爲 Service Mesh。
Service Mesh 未來展望
從目前趨勢看來,Istio 仍然會作爲 Service Mesh 控制平面的首選。儘管 Istio 的 CRD 對用戶也不是很友好,但是 Istio 定義的 Service Mesh 標準體系目前仍是最完整的,也獲得了最廣泛的數據平面的支持。
而數據平面,則可能出現百花齊放的場景,畢竟業務場景非常多樣,有的看重靈活性,有的看重性能,有的看重安全,那麼就能催生不同的數據平面實現方案。Envoy 仍然會作爲 Proxy 模式的主流選擇,但各 RPC 框架也不甘於只做瘦客戶端,將會繼續發展自己的 Proxyless 方案,讓 Service Mesh 能落地到更多的業務場景之中。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8T7XI6jQfZunwVYDaDHvLw