美團分布式服務治理框架OCTO之二:Mesh化
寫在前面
前面的文章主要介紹了美團Octo服務治理框架,隨著云原生的崛起,大量服務治理體系普遍“云原生”化,而Mesh則是云原生中非常重要的一個流派,今天我們看下美團的Octo是如何一步步的Mesh化的。
Mesh化
經(jīng)過一整套服務治理能力的升級,原有Octo已經(jīng)支持了,包括set化、鏈路級復雜路由、全鏈路壓測、鑒權加密、限流熔斷等治理能力。
但整個治理體系仍存在一些痛點及挑戰(zhàn):
多語言支持不友好:每一個語言搞一套治理體系不現(xiàn)實
中間件和業(yè)務綁定在一起,彼此制約迭代:原有的一些治理能力是通過入侵業(yè)務代碼實現(xiàn)的,比如filter、api、sdk集成等,只是做到了邏輯隔離,未做到物理上的隔離。一般來說核心的治理能力主要由通信框架承載,如果沒有更進一步的隔離(如物理隔離),那中間件引入的bug就需要所有業(yè)務配合升級,對業(yè)務研發(fā)效率造成傷害
治理決策比較分散:每個節(jié)點根據(jù)自己的狀態(tài)進行決策,無法與其他節(jié)點協(xié)同仲裁
針對于以上問題,Octo升級到2.0架構,引入了Mesh概念。
Mesh模式下,為每個業(yè)務實例部署一個Sidecar代理,所有進出應用的業(yè)務流量統(tǒng)一由Sidecar承載,同時服務治理的工作也由Sidecar執(zhí)行,所有的Sidecar由統(tǒng)一的中心化大腦控制面進行全局管控。
做好Mesh化升級,怎么解決上面的痛點的呢:
Service Mesh 模式下,各語言的通信框架一般僅負責編解碼,而編解碼的邏輯往往是不變的。核心的治理功能(如路由、限流等)主要由 Sidecar 代理和控制大腦協(xié)同完成,從而實現(xiàn)一套治理體系,所有語言通用。
中間件易變的邏輯盡量下沉到 Sidecar 和控制大腦中,后續(xù)升級中間件基本不需要業(yè)務配合。SDK 主要包含很輕薄且不易變的邏輯,從而實現(xiàn)了業(yè)務和中間件的解耦。
新融入的異構技術體系可以通過輕薄的 SDK 接入美團治理體系(技術體系難兼容,本質是它們各自有獨立的運行規(guī)范,在 Service Mesh 模式下運行規(guī)范核心內容就是控制面和Sidecar),目前美團線上也有這樣的案例。
控制大腦集中掌控了所有節(jié)點的信息,進而可以做一些全局最優(yōu)的決策,比如服務預熱、根據(jù)負載動態(tài)調整路由等能力。
總結起來:盡量將治理能力與業(yè)務邏輯剝離開來,通過輕量級的SDK與業(yè)務邏輯耦合,但這一部分設計需要盡量輕薄,更多治理能力下沉到SideCar與控制大腦。
整體架構
Octo2.0整體架構如下:

協(xié)作系統(tǒng)包括服務治理系統(tǒng)、鑒權服務、配置中心、限流服務等,這些原有服務治理能力在Mesh架構下是可復用的,無需重復開發(fā)。
Mesh的技術選型
美團的Mesh改造起步于2018年底,當時的一個核心問題是整體方案的考量應該關注于哪幾個方面。
啟動計劃階段時,有了一些非常明確的關注點:
Octo體系經(jīng)過五年的迭代,形成了一系列的標準與規(guī)范,進行Mesh改造治理體系升級范圍會非常大,在確保技術方案可以落地的同事,也要屏蔽技術升級對于業(yè)務改動
治理能力不能減弱,在保障對齊的基礎上逐漸提供更精細、更易用的運營能力
可以應對超大規(guī)模的挑戰(zhàn),技術方案需要確保支撐當前兩級甚至N倍的增量,系統(tǒng)自身不能成為整個治理體系的瓶頸
盡量與社區(qū)保持親和,一定程度上和社區(qū)協(xié)同演進
于是產(chǎn)出了如下的技術選型方案:

于是選擇了一種數(shù)據(jù)層面基于Envoy二次開發(fā),控制碼自研的整體選型與方案。
數(shù)據(jù)面方面,當時 Envoy 有機會成為數(shù)據(jù)面的事實標準,同時 Filter 模式及 xDS 的設計對擴展比較友好,未來功能的豐富、性能優(yōu)化也與標準關系較弱。
控制面自研為主的決策需要考量的內容就比較復雜,總體而言需要考慮如下幾個方面:
美團容器化主要采用富容器的模式,這種模式下強行與 Istio 及 Kubernetes 的數(shù)據(jù)模型匹配改造成本極高,同時 Istio API也尚未確定。
Istio 在集群規(guī)模變大時較容易出現(xiàn)性能問題,無法支撐美團數(shù)萬應用、數(shù)十萬節(jié)點的的體量,同時數(shù)十萬節(jié)點規(guī)模的 Kubernetes 集群也需要持續(xù)優(yōu)化探索。
Istio 的功能無法滿足 OCTO 復雜精細的治理需求,如流量錄制回放壓測、更復雜的路由策略等。
項目啟動時非容器應用占比較高,技術方案需要兼容存量非容器應用。
整體Mesh方案如下:

這張圖展示了 OCTO Mesh 的整體架構。從下至上來看,邏輯上分為業(yè)務進程及通信框架 SDK 層、數(shù)據(jù)平面層、控制平面層、治理體系協(xié)作的所有周邊生態(tài)層。
先來重點介紹下業(yè)務進程及SDK層、數(shù)據(jù)平面層:
OCTO Proxy (數(shù)據(jù)面Sidecar代理內部叫OCTO Proxy)與業(yè)務進程采用1對1的方式部署。
OCTO Proxy 與業(yè)務進程采用 UNIX Domain Socket 做進程間通信(這里沒有選擇使用 Istio 默認的 iptables 流量劫持,主要考慮美團內部基本是使用的統(tǒng)一化私有協(xié)議通信,富容器模式?jīng)]有用 Kubernetes 的命名服務模型,iptables 管理起來會很復雜,而 iptables 復雜后性能會出現(xiàn)較高的損耗。);OCTO Proxy 間跨節(jié)點采用 TCP 通信,采用和進程間同樣的協(xié)議,保證了客戶端和服務端具備獨立升級的能力。
為了提升效率同時減少人為錯誤,我們獨立建設了 OCTO Proxy 管理系統(tǒng),部署在每個實例上的 LEGO Agent 負責 OCTO Proxy 的?;詈蜔嵘?,類似于 Istio 的 Pilot Agent,這種方式可以將人工干預降到較低,提升運維效率。
數(shù)據(jù)面與控制面通過雙向流式通信。路由部分交互方式是增強語義的 xDS,增強語義是因為當前的 xDS 無法滿足美團更復雜的路由需求;除路由外,該通道承載著眾多的治理功能的指令及配置下發(fā),我們設計了一系列的自定義協(xié)議。

控制面(美團內部名稱為Adcore)自研為主,整體分為:Adcore Pilot、Adcore Dispatcher、集中式健康檢查系統(tǒng)、節(jié)點管理模塊、監(jiān)控預警模塊。此外獨立建設了統(tǒng)一元數(shù)據(jù)管理及 Mesh 體系內的服務注冊發(fā)現(xiàn)系統(tǒng) Meta Server 模塊。
每個模塊的具體職責如下:
Adcore Pilot 是個獨立集群,模塊承載著大部分核心治理功能的管控,相當于整個系統(tǒng)的大腦,也是直接與數(shù)據(jù)面交互的模塊。
Adcore Dispatcher 也是獨立集群,該模塊是供治理體系協(xié)作的眾多子系統(tǒng)便捷接入 Mesh 體系的接入中心。
不同于 Envoy 的 P2P 節(jié)點健康檢查模式,OCTO Mesh 體系使用的是集中式健康檢查。
控制面節(jié)點管理系統(tǒng)負責采集每個節(jié)點的運行時信息,并根據(jù)節(jié)點的狀態(tài)做全局性的最優(yōu)治理的決策和執(zhí)行。
監(jiān)控預警系統(tǒng)是保障 Mesh 自身穩(wěn)定性而建設的模塊,實現(xiàn)了自身的可觀測性,當出現(xiàn)故障時能快速定位,同時也會對整個系統(tǒng)做實時巡檢。
與Istio 基于 Kubernetes 來做尋址和元數(shù)據(jù)管理不同,OCTO Mesh 由獨立的 Meta Server 負責 Mesh 自身眾多元信息的管理和命名服務。
實現(xiàn)原理
我們看下核心Mesh架構實現(xiàn)原理。
流量劫持
Octo并未采用Istio的原生方案,而是使用iptables對進出POD的流量進行劫持:
iptables自身存在性能損失大、管控性差的問題:
iptables在內核對于包的處理過程中定義了五個“hook point”,每個“hook point”各對應到一組規(guī)則鏈,outbond流量將兩次穿越協(xié)議棧并且經(jīng)過這5組規(guī)則鏈匹配,在大并發(fā)場景下會損失轉發(fā)性能。 iptables全局生效,不能顯式地禁止相關規(guī)則的修改,沒有相關ACL機制,可管控性比較差。 在美團現(xiàn)有的環(huán)境下,使用iptables存在以下幾個問題:
HULK容器為富容器形態(tài),業(yè)務進程和其他所有基礎組件都處于同一容器中,這些組件使用了各種各樣的端口,使用iptables容易造成誤攔截。 美團現(xiàn)在存在物理機、虛擬機、容器等多個業(yè)務運行場景,基于iptables的流量劫持方案在適配這些場景時復雜度較高。
鑒于以上兩個問題,最終采用了Unix Domain Socket直連方式,實現(xiàn)了業(yè)務進程和Octo Proxy進程之間的流量轉發(fā)。
服務消費者一方,業(yè)務進程通過輕量級的Mesh SDK和Octo Proxy監(jiān)聽的UDS地址建立連接。
服務提供者一方,Octo Proxy代替業(yè)務進程監(jiān)聽在TCP端口上,業(yè)務進程則監(jiān)聽在制定的UDF地址上。
UDS相比于iptable劫持有更好的性能和更低的運維成本,缺點是需要SDK。
服務訂閱
原生的Envoy的CDS、EDS請求時全量服務發(fā)現(xiàn)模式,是將系統(tǒng)中所有的服務列表都請求到數(shù)據(jù)面來進行處理。
由于大規(guī)模服務集群的服務數(shù)量太多,而需要的服務信息是少數(shù)的,所以需要改造成按需獲取服務的發(fā)現(xiàn)模式,只需要請求要訪問的后端服務節(jié)點列表就可以了。

流程如下:
業(yè)務進程啟動之后,通過http方式向Octo proxy發(fā)起服務訂閱請求,Octo Proxy將所要請求的后端AppKey更新到Xds中,Xds在向控制面請求具體的服務資源。
為增加整個過程健壯性,降低后期運維成本,做了一定的優(yōu)化。比如Octo Proxy的啟動速度有可能比業(yè)務進程啟動慢,所以Mesh SDK中增加了請求重試的邏輯,確保請求真正可以經(jīng)由Octo Proxy發(fā)出去。
Mesh SDK和Octo Proxy之間的http請求改成了同步請求,防止pilot資源下發(fā)延遲帶來問題。
Mesh SDK的訂閱信息也會保存在本地文件中,以便在Octo Proxy重啟或更新過程中,服務的可用性。
無損熱重啟
由于業(yè)務進程和Octo Proxy是獨立的進程,確保Proxy進程熱更新時可以持續(xù)提供服務,對業(yè)務無損無感知就非常重要。社區(qū)的Envoy自己支持的熱重啟不夠完善,不能做到完全的無損流量。
我們看下在短連接和長連接兩種情況下Octo Proxy重啟可能造成的流量損耗問題。

在短連接場景下,所有的新連接會在Octo Proxy New上創(chuàng)建,Octo Proxy Old上已有的連接會在響應到來后主動斷開。Octo Proxy Old的所有短連接逐漸斷開,當所有連接斷開之后,Octo Proxy Old主動退出,Octo Proxy New繼續(xù)工作,整個過程中流量是無損的。
在長連接場景下,SDK和Octo Proxy Old之間維持一個長連接斷不開,并持續(xù)使用這個連接發(fā)送請求。Ocot Proxy Old進程最終退出時,該鏈接才被迫斷開,這時可能有部分請求還未返回,導致Client端請求超時,因此Envoy的熱重啟對長連接場景支持的不完美。
為實現(xiàn)基礎組件更新過程不對業(yè)務流量造成損耗,業(yè)界的主要方式是滾動發(fā)布。也就是,不是直接全部更新,而是一部分一部分的更新,滾動的承接流量+主動斷開連接。
服務節(jié)點分批停止服務,執(zhí)行更新,然后重啟,投入使用,直到集群中所有實例都更新為最新版本。這個過程中會主動摘到業(yè)務流量,保證升級過程中業(yè)務流量不丟失。

美團的方案是進行角色劃分,將業(yè)務服務分為兩個角色:
對外提供服務的server端;
發(fā)起請求調用的client端;
client端octo proxy熱更新:
octo proxy old進入重啟狀態(tài),對后續(xù)的請求直接返回“熱更新”標志的響應協(xié)議,client sdk在收到“熱更新”的協(xié)議標識之后,主動切換連接進行重試。然后斷開sdk上和octo proxy old之間的長連接。

通過client sdk和octo proxy之間的交互配合,可以實現(xiàn)client在octo proxy升級過程中的流量安全。
server端coto proxy熱更新:
server端的octo proxy在熱更新開始后,主動向client側的octo proxy發(fā)送proxy restart消息,也就是要求client側的octo proxy主動切換新連接,避免當前client側octo proxy持有的舊鏈接被強制關閉,導致請求失敗。
client側octo proxy收到“主動切換新連接”的請求后,應及時從可用連接池中清除老的長連接。

數(shù)據(jù)面運維
在云原生環(huán)境下,Envoy運行在標準的K8s Pod中,通常會獨立出一個Sidecar容器,這樣可以借助K8s的能力實現(xiàn)對Envoy Sidecar容器的管理,比如容器注入、健康檢查、滾動升級、資源限制等。
美團內部的容器運行時模式為:單容器模式。就是在一個pod內只包含一個容器。
由于業(yè)務進程和所有基礎組件都運行在一個容器中,所以只能采用進程粒度的管理措施,無法做到容器粒度的管理。

Lego Agent支持了對Octo Proxy熱更新的感知,還負責對Octo Proxy進行健康檢查、故障狀態(tài)重啟、監(jiān)控信息上報和版本發(fā)布等。相對于原生k8s的容器重啟方式,進程粒度重啟會更快。
擴展性及完善的運維體系
關鍵設計
大規(guī)模治理體系 Mesh 化建設成功落地的關鍵點有:
系統(tǒng)水平擴展能力方面,可以支撐數(shù)萬應用/百萬級節(jié)點的治理。
功能擴展性方面,可以支持各類異構治理子系統(tǒng)融合打通。
能應對 Mesh 化改造后鏈路復雜的可用性、可靠性要求。
具備成熟完善的 Mesh 運維體系。
圍繞這四點,便可以在系統(tǒng)能力、治理能力、穩(wěn)定性、運營效率方面支撐美團當前多倍體量的新架構落地。

對于社區(qū) Istio 方案,要想實現(xiàn)超大規(guī)模應用集群落地,需要完成較多的技術改造。
因為 Istio 水平擴展能力相對薄弱,內部冗余操作多,整體穩(wěn)定性較為薄弱。
解決思路如下:
控制面每個節(jié)點并不承載所有治理數(shù)據(jù),系統(tǒng)整體做水平擴展。
在此基礎上提升每個實例的整體吞吐量和性能。
當出現(xiàn)機房斷網(wǎng)等異常情況時,可以應對瞬時流量驟增的能力。
只做必要的 P2P 模式健康檢查,配合集中式健康檢查進行百萬級節(jié)點管理。

按需加載和數(shù)據(jù)分片主要由 Adcore Pilot、Meta Server 實現(xiàn)。
Pilot 的邏輯是管理每個數(shù)據(jù)面會話的全生命周期、會話的創(chuàng)建、交互及銷毀等一系列動作及流程;
維護數(shù)據(jù)最新的一致性快照,對下將資源更新同步處理,對上響應各平臺的數(shù)據(jù)變更通知,將存在關聯(lián)關系的一組數(shù)據(jù)做快照緩存。
控制面每個 Pilot 節(jié)點并不會把整個注冊中心及其他數(shù)據(jù)都加載進來,而是按需加載自己管控的 Sidecar 所需要的相關治理數(shù)據(jù)。
同一個應用的所有 OCTO Proxy 由同一個Pilot 實例管控,Meta Server,自己實現(xiàn)控制面機器服務發(fā)現(xiàn)和精細化控制路由規(guī)則,從而在應用層面實現(xiàn)了數(shù)據(jù)分片。

Meta Server 管控每個Pilot節(jié)點和OCTO Proxy的歸屬關系。
當 Pilot 實例啟動后會注冊到 Meta Server,此后定時發(fā)送心跳進行續(xù)租,長時間心跳異常會自動剔除。
Meta Server 內部有一致性哈希策略,會綜合節(jié)點的應用、機房、負載等信息進行分組。當一個 Pilot 節(jié)點異常或發(fā)布時,該 Pilot 的 OCTO Proxy 都會有規(guī)律的連接到接替節(jié)點,而不會全局隨機連接對后端注冊中心造成風暴。
當異?;虬l(fā)布后的節(jié)點恢復后,劃分出去的 OCTO Proxy 又會有規(guī)則的重新歸屬當前 Pilot 實例管理。
對于關注節(jié)點特別多的應用 OCTO Proxy,也可以獨立部署 Pilot,通過 Meta Server 統(tǒng)一進行路由管理。

穩(wěn)定性保障設計

圍繞控制故障影響范圍、異常實時自愈、可實時回滾、柔性可用、提升自身可觀測性及回歸能力進行建設。

命名服務與注冊中心打通
Mesh體系的命名服務需要 Pilot 與注冊中心打通。
采用ZK實現(xiàn)的方式是每個 OCTO Proxy 與 Pilot 建立會話時,作為客戶端角色會向注冊中心訂閱自身所關注的服務端變更監(jiān)聽器,如果這個服務需要訪問100個應用,則至少需要注冊100個 Watcher 。
如果存在1000個實例同時運行,就會注冊 100 x 1000 = 100000 個 Watcher。還有很多應用有相同的關注的對端節(jié)點,造成大量的冗余監(jiān)聽。
規(guī)模較大后,網(wǎng)絡抖動或業(yè)務集中發(fā)布時,很容易引發(fā)風暴效應把控制面和后端的注冊中心打掛。
針對這個問題,可以采用分層訂閱方式。
就是每個 OCTO Proxy 的會話并不直接和注冊中心或其他的發(fā)布訂閱系統(tǒng)交互,而是將變更的通知全部由 Snapshot 快照層管理。
Snapshot 內部又劃分為3層:
Data Cache 層對接并緩存注冊中心及其他系統(tǒng)的原始數(shù)據(jù),粒度是應用;
Node Snapshot 層則是保留經(jīng)過計算的節(jié)點粒度的數(shù)據(jù);
Ability Manager 層內部會做索引和映射的管理,當注冊中心存在節(jié)點狀態(tài)變更時,會通過索引將變更推送給關注變更的 OCTO Proxy;
回到剛才的場景,隔離一層后1000個節(jié)點僅需注冊100個 Watcher,一個 Watcher 變更后僅會有一條變更信息到 Data Cache 層,再根據(jù)索引向1000個 OCTO Proxy 通知,從而極大的降低了注冊中心及 Pilot 的負載。

Snapshot 層除了減少不必要交互提升性能外,還會將計算后的數(shù)據(jù)格式化緩存下來,這樣瞬時大量的請求會在快照層被緩存擋住。
預加載的主要目的是提升服務冷啟動性能。
在 Pilot 節(jié)點中加載好最新的數(shù)據(jù),當業(yè)務進程啟動時,Proxy 就可以立即從 Snapshot 中獲取到數(shù)據(jù),避免了首次訪問慢的問題。

Istio 默認每個 Envoy 代理對整個集群中所有其余 Envoy 進行 P2P 健康檢測。
當集群有N個節(jié)點時,一個檢測周期內就需要做N的平方次檢測,另外當集群規(guī)模變大時,所有節(jié)點的負載就會相應提高,這都將成為擴展部署的極大障礙。
美團采用了集中式的健康檢查方式,同時配合必要的P2P檢測:
由中心服務 Scanner 監(jiān)測所有節(jié)點的狀態(tài),當 Scanner 主動檢測到節(jié)點異?;?Pilot 感知連接變化通知 Scanner 掃描確認節(jié)點異常時, Pilot 立刻通過 eDS 更新節(jié)點狀態(tài)給 Proxy,這種模式下檢測周期內僅需要檢測 N 次。(Google 的Traffic Director 也采用了類似的設計,但大規(guī)模使用需要一些技巧:第一個是為了避免機房自治的影響而選擇了同機房檢測方式,第二個是為了減少中心檢測機器因自己 GC 或網(wǎng)絡異常造成誤判,而采用了Double Check 的機制)。
除了集中健康檢查,還會對頻繁失敗的對端進行心跳探測,根據(jù)探測結果進行摘除操作,提升成功率。
異構治理系統(tǒng)融合設計
Istio 和 Kubernetes 將所有的數(shù)據(jù)存儲、發(fā)布訂閱機制都依賴 Etcd 統(tǒng)一實現(xiàn),但美團的10余個治理子系統(tǒng)功能各異、存儲各異、發(fā)布訂閱模式各異,呈現(xiàn)出明顯的異構特征,如果接入一個功能就需要平臺進行存儲或其他大規(guī)模改造,這樣是完全不可行的。
一個思路是由一個模塊來解耦治理子系統(tǒng)與 Pilot ,這個模塊承載所有的變更并將這個變更下發(fā)給 Pilot。
獨立的統(tǒng)一接入中心,屏蔽所有異構系統(tǒng)的存儲、發(fā)布訂閱機制;
Meta Server 承擔實時分片規(guī)則的元數(shù)據(jù)管理;

執(zhí)行機制如上圖:
各系統(tǒng)變更時使用客戶端將變更通知推送到消息隊列,只推送變更但不包含具體值(當Pilot接收到變更通知后,會主動Fetch全量數(shù)據(jù),這種方式一方面確保Mafka的消息足夠小,另一方面多個變更不需要在隊列中保序解決版本沖突問題。);
Adcore Dispatcher 消費信息,并根據(jù)索引將變更推送到關注的 Pilot 機器,當 Pilot 管控的 Proxy 變更時會同步給 Meta Server,Meta Server 實時將索引關系更新并同步給Dispatcher;
為了解決 Pilot 與應用的映射變更間隙出現(xiàn)消息丟失,Dispatcher 使用回溯檢驗變更丟失的模式進行補償,以提升系統(tǒng)的可靠性;
運維體系設計

操作流程如下:
運維人員在 LEGO 平臺發(fā)版,確定發(fā)版版本;
新版本資源內容上傳至資源倉庫,并更新規(guī)則及發(fā)版范圍至 DB;
升級指令下發(fā)至所要發(fā)布的范圍;
收到發(fā)版命令機器的 LEGO Agent 去資源倉庫拉取要更新的版本(如有失敗,會有主動 Poll 機制保證升級成功);
新版本下載成功后,由 LEGO Agent 啟動新版的 OCTO Proxy;
總結
美團的Mesh方案已經(jīng)看不懂了,看不懂只能后續(xù)再熟悉Mesh一些回頭再看。
整體看來,美團這套Mesh演進方案對大家還是非常有借鑒意義的,因為上Mesh勢必是已經(jīng)到了一定的治理規(guī)模,這里會遇到一個很重要的問題是,如何將Mesh的治理能力有機的集成到已經(jīng)成熟的某套治理能力下,比如微服務治理體系。
所以這種面向未來新的治理體系來了之后,更多的問題是如何借鑒新模式方案方式去優(yōu)化我們的系統(tǒng)中,簡單說是要個“神”,而不必是“形”,比如你可以按需替換掉Mesh里面某些模塊、組件等,以我們成熟的能力去承接。
