攜程Service Mesh性能優(yōu)化實(shí)踐
作者簡(jiǎn)介
本文作者佐思、燒魚、Shirley博,來自于攜程Cloud Container團(tuán)隊(duì),主要從事Service Mesh在攜程的落地,負(fù)責(zé)控制面的可用性及優(yōu)化建設(shè),以及推進(jìn)各類基礎(chǔ)設(shè)施服務(wù)的云原生化。該團(tuán)隊(duì)負(fù)責(zé)K8s容器平臺(tái)的研發(fā)和優(yōu)化工作,專注于推動(dòng)基礎(chǔ)設(shè)施云原生架構(gòu)升級(jí),以及創(chuàng)新產(chǎn)品的研發(fā)和落地。
一、背景
為了支撐業(yè)務(wù)的高速發(fā)展,從17年開始,攜程內(nèi)部逐步推進(jìn)應(yīng)用容器化改造與業(yè)務(wù)上云工作,同期攜程技術(shù)架構(gòu)經(jīng)歷了從集中式單體應(yīng)用到分布式微服務(wù)化的演進(jìn)過程。
隨著Kubernetes的不斷發(fā)展和推廣,服務(wù)網(wǎng)格(Service Mesh)在近幾年也變得很流行。而 Servive Mesh 之所以越來越受歡迎,在提供更豐富的服務(wù)治理、安全性、可觀測(cè)性等核心能力外,其從架構(gòu)設(shè)計(jì)層面解決了以上幾個(gè)痛點(diǎn),服務(wù)治理能力以 Sidecar 的模式下沉到數(shù)據(jù)面,解決了 SDK 升級(jí)及多語言的問題,對(duì)于像負(fù)載均衡、熔斷、限流等策略配置,由控制面統(tǒng)一管理和配置,并下發(fā)到數(shù)據(jù)面生效。在整體架構(gòu)上云技術(shù)方案選型上,權(quán)衡各類方案的功能完備性、架構(gòu)擴(kuò)展性、改造維護(hù)成本及社區(qū)發(fā)展等,最終選擇基于Istio構(gòu)建Service Mesh平臺(tái)治理方案。
1.1 攜程Service Mesh發(fā)展
從2020年中,我們依托K8S底座能力,進(jìn)行Service Mesh技術(shù)預(yù)研,深度定制Istio,并與攜程框架部門合作進(jìn)行了小規(guī)模的落地試點(diǎn)。2021年底,接入非核心應(yīng)用600+,為Service Mesh在攜程的最終落地奠定基礎(chǔ)。到目前為止,生產(chǎn)環(huán)境已有2000個(gè)應(yīng)用(業(yè)務(wù)POD數(shù)近1W)接入,預(yù)期下半年推進(jìn)核心應(yīng)用的接入。

1.2 攜程Service Mesh數(shù)據(jù)表現(xiàn)
在前期應(yīng)用接入過程中,針對(duì)Istio穩(wěn)定性(主要在性能)方面,梳理了以下幾個(gè)問題:
控制面并發(fā)性能:pilot對(duì)象處理的并發(fā)性能是否滿足平臺(tái)需求?
控制面配置下發(fā)時(shí)效性:配置下發(fā)的延遲及準(zhǔn)確性是否能夠滿足業(yè)務(wù)需求?
本文主要分享在當(dāng)前的體量下,回答上述問題,使控制面平穩(wěn)支撐大規(guī)模 Sidecar 的落地,通過下述優(yōu)化之后,測(cè)試域(Istio CR量級(jí)在1W+)如下圖所示:



- 從實(shí)際生產(chǎn)來看,ServiceEntry的處理效率提升了15倍左右
 - 從測(cè)試域來看,整體initContext時(shí)延從原先P99 30s左右到目前P99 5-10秒左右
 - 從測(cè)試域來看,整體優(yōu)化水平從原先P99 30s+到目前的P99 15s左右(該處為全量推送水平,其中15s的結(jié)果是平衡資源使用與推送效率調(diào)參的目標(biāo)值,這里PILOT_DEBOUNCE_AFTER 設(shè)置為10s)
 - 開啟增量推送后,實(shí)例推送在測(cè)試域的推送效果從10-30s縮減至2.5s左右
 
攜程目前Istio落地版本為1.10
二、Service Mesh優(yōu)化的思路與挑戰(zhàn)
2.1 現(xiàn)狀
針對(duì)Service Mesh在攜程落地的服務(wù)目標(biāo),可以用一句話進(jìn)行總結(jié):能夠通過橫向擴(kuò)展,支撐萬級(jí)業(yè)務(wù)服務(wù)。為了完成上述目標(biāo),團(tuán)隊(duì)面臨以下挑戰(zhàn):
- 當(dāng)前 Istio的對(duì)象處理性能等方面無法滿足平臺(tái)需求
 - 配置下發(fā)的延遲及準(zhǔn)確性無法滿足業(yè)務(wù)需求
 
經(jīng)過前中期,針對(duì) Istio架構(gòu)進(jìn)行深入研究以及上線前期測(cè)試的性能預(yù)研,核心問題聚焦在以下幾點(diǎn):
- istio對(duì)象處理性能較低:在處理ServiceEntry時(shí)的并發(fā)性缺失及WorkloadEntry Selector模式的計(jì)算高耗時(shí)等
 - istio配置推送性能較低:配置推送時(shí)對(duì)象的全量處理拉長(zhǎng)下發(fā)時(shí)延,并會(huì)隨著Istio對(duì)象增長(zhǎng)而近線性增長(zhǎng)
 
2.2 優(yōu)化實(shí)踐
接下來主要分享攜程所經(jīng)歷過的性能問題,和對(duì)應(yīng)的優(yōu)化的方向:
對(duì)象處理性能
當(dāng)前istio使用內(nèi)部queue處理各類Object事件,其為線性處理流程,效率比較低下,為此社區(qū)提供namespace filter方式進(jìn)行處理,以減緩性能問題,但針對(duì)Istio相關(guān)對(duì)象,未實(shí)現(xiàn)基于namespace的隔離,因此效率提升不太符合預(yù)期。
推送性能
xDS 是 istio 控制面和數(shù)據(jù)面 envoy 之間的通信協(xié)議,其中x 表示多種協(xié)議的集合,可以簡(jiǎn)單的把 xDS 理解為,網(wǎng)格內(nèi)的服務(wù)發(fā)現(xiàn)數(shù)據(jù)和治理規(guī)則的集合。 xDS 數(shù)據(jù)量的大小和網(wǎng)格規(guī)模是正相關(guān)的。
當(dāng)前 istio 下發(fā) xDS 使用的是全量下發(fā) 策略,也就是網(wǎng)格里的所有 sidecar,內(nèi)存里都會(huì)有整個(gè)網(wǎng)格內(nèi)所有的服務(wù)發(fā)現(xiàn)數(shù)據(jù)。 在大量服務(wù)實(shí)例的情況下,全量下發(fā)會(huì)影響 Pilot 和 Sidecar 的性能和穩(wěn)定性,雖然Istio 在不斷的演進(jìn)過程中引入了一些 scoping 的機(jī)制,就是 Sidecar 這個(gè) CRD,這個(gè)配置可以顯式的定義服務(wù)之間的依賴關(guān)系,但該scoping方案還是無法達(dá)到業(yè)務(wù)側(cè)的推送延遲預(yù)期。
首先,簡(jiǎn)要介紹一下Istio推送的過程:

注:這里使用海東同學(xué)的推送源碼分析圖
根據(jù)上圖結(jié)合源碼可知:
- StreamAggregatedResources 會(huì)和當(dāng)前的 Proxy 創(chuàng)建一個(gè)連接,并創(chuàng)建一個(gè)接受請(qǐng)求的 reqChannel 。同時(shí)開啟一個(gè)新的協(xié)程 receiveThread 處理客戶端主動(dòng)發(fā)起的請(qǐng)求,期間調(diào)用 s.globalPushContext().InitContext(s.Env, nil, nil) 進(jìn)行數(shù)據(jù)初始化,其中InitContext需要處理Istio 所有CR的全量數(shù)據(jù)(如VirtualServices、DestinationRules、EnvoyFilters和SidecarScopes等),該操作耗時(shí)較長(zhǎng),因測(cè)試環(huán)境上述對(duì)象量級(jí)在2w左右,導(dǎo)致執(zhí)行耗時(shí)P99 在28s左右。
 - 從 con.pushConnection 中獲取到 pushEv 事件后,調(diào)用 s.pushConnection() 進(jìn)行處理,判斷是否為全量推送:
 
if pushRequest.Full {
// Update Proxy with current information.
s.updateProxy(con.proxy, pushRequest.Push)
}
其中 updateProxy 更新proxy當(dāng)前信息,主要邏輯如下所示:
func (s *DiscoveryServer) updateProxy(proxy *model.Proxy, push *model.PushContext) {
s.setProxyState(proxy, push)
if util.IsLocalityEmpty(proxy.Locality) {
if len(proxy.ServiceInstances) > 0 {
proxy.Locality = util.ConvertLocality(proxy.ServiceInstances[0].Endpoint.Locality.Label)
}
}
}
func (s *DiscoveryServer) setProxyState(proxy *model.Proxy, push *model.PushContext) {
proxy.SetWorkloadLabels(s.Env)
proxy.SetServiceInstances(push.ServiceDiscovery)
proxy.SetSidecarScope(push)
proxy.SetGatewaysForProxy(push)
}
在 setProxyState 方法中的環(huán)節(jié)獲取SidecarScope等相關(guān)信息。針對(duì)上述介紹可以明確下面幾個(gè)優(yōu)化方向:
雖然Istio針對(duì)K8S對(duì)象實(shí)現(xiàn)了基于namespace級(jí)別的隔離,但未對(duì)Istio CR對(duì)象實(shí)現(xiàn)namespace級(jí)別隔離
在 InitContext 方法中, Push()  這么慢,主要是在  req.Full  做全量推送的時(shí)候,需要初始化  PushContext  ,初始化  PushContext  的過程中需要調(diào)用  initServiceRegistry 、 initEnvoyFilters 和 initSidecarScopes 等,耗時(shí)巨大
2.2.1 Pilot性能優(yōu)化
資源基于Namespace隔離
雖然Istio針對(duì)K8S對(duì)象實(shí)現(xiàn)了基于namespace級(jí)別的隔離,但未對(duì)Istio CR對(duì)象實(shí)現(xiàn)namespace級(jí)別隔離,基于此,內(nèi)部團(tuán)隊(duì)針對(duì)Istio 1.10.3版本針對(duì)Istio CR對(duì)象實(shí)現(xiàn)namespace隔離,使其影響范圍控制在指定namespace 下,其他用戶操作Istio CR而彼此互不干擾,且能夠極大縮減Istio Event事件的處理,加速Pilot啟動(dòng)速度,提升事件處理效率,促進(jìn)配置下發(fā)效率,在CR Client結(jié)構(gòu)體中,新增 namespaceFilter 等相關(guān)字段,定義如下:
// Client is a client for Istio CRDs, implementing config store cache
// This is used for CRUD operators on Istio configuration, as well as handling of events on config changes
type Client struct {
namespaceInformer v1.NamespaceInformer
namespaceFilter filter.DiscoveryNamespacesFilter
}
截至目前為止,攜程Mesh平臺(tái)主要分為SLB、SOA兩大namespace,基于namespace隔離之后,效率提升預(yù)估在50%左右。
ServiceEntryStore 改造
ServiceEntryStore 的數(shù)據(jù)處理性能問題,主要有以下幾點(diǎn):
- 它里面有一個(gè)步驟,會(huì)全量更新實(shí)例的索引,這意味著 service 有一個(gè)發(fā)生變化了,它會(huì)更新全部 service 的索引,這是一個(gè)量級(jí)寫放大
 - WorkloadEntry與ServiceEntry的關(guān)聯(lián)查詢的耗時(shí),隨著彼此的數(shù)量逐步放大
 - configController的Queue隊(duì)列為線性處理,效率低下
 
因此,攜程通過針對(duì)ServiceEntryStore進(jìn)行Controller-Runtime改造,將ServiceEntry對(duì)象由線性處理改為并發(fā)處理,同時(shí)將WorkloadEntry結(jié)構(gòu)體廢棄,選擇直接使用ServiceEntry,業(yè)務(wù)方Operator管理ServiceEntry對(duì)應(yīng)Endpoint方式,優(yōu)化處理性能,從實(shí)際生產(chǎn)效率來看,ServiceEntry的處理效率提升了4倍左右。
EnvoyFilter增量化改造
通過上述簡(jiǎn)介可知, Push() 這么慢,主要是在 req.Full 做全量推送的時(shí)候,需要初始化 PushContext ,初始化 PushContext 的過程,都是全量且嵌套循環(huán)處理,因此當(dāng)多個(gè)對(duì)象量級(jí)巨大,則計(jì)算耗時(shí)成倍增長(zhǎng),針對(duì)EnvoyFilter的全量處理,不涉及其他對(duì)象,可以通過定義EnvoyFilterController結(jié)構(gòu)體以Controller方式運(yùn)行,從而實(shí)現(xiàn)全量改增量,結(jié)構(gòu)體定義如下:
type Controller struct {
xdsUpdater model.XDSUpdater
client kube.Client
queue controllers.Queue
// processed ingresses
envoyFilter map[types.NamespacedName]*wrapEnvoyFilterWrapper
envoyFilterInformer cache.SharedInformer
envoyFilterLister v1alpha3.EnvoyFilterLister
mutex sync.Mutex
envoyFiltersByNamespace map[string][]*wrapEnvoyFilterWrapper
}
Sidecar延遲及按需計(jì)算
在 InitContext 方法中,除了EnvoyFilter耗時(shí)較多外,initSidecarScopes同樣耗時(shí)巨大,通過代碼可知, Sidecar 有兩種,一種是帶 WorkloadSelector 的,一種是不帶的。不帶 Selector 的話就是對(duì)這個(gè)命名空間所有服務(wù)生效。如果沒有手動(dòng)創(chuàng)建默認(rèn)的 Sidecar ,Pilot 會(huì)通過 DefaultSidecarScopeForNamespace 為當(dāng)前命名空間創(chuàng)建一個(gè)默認(rèn)的 Sidecar ,會(huì)將網(wǎng)格中所有的服務(wù)都遍歷一遍,寫入 SidecarScope 中。 initSidecarScopes 循環(huán)計(jì)算如下:
sidecar數(shù)量x(egressConfigs數(shù)量x(selectVirtualServices耗時(shí)+selectServices耗時(shí))+out.EgressListeners數(shù)量x(listener.services數(shù)量+listener.virtualServices數(shù)量))
因SidecarScope涉及其他CR對(duì)象結(jié)果,因此無法簡(jiǎn)單的由全量改增量,但可以通過延遲計(jì)算和按需計(jì)算方式,進(jìn)行效率提升,延遲計(jì)算主要通過將 initSidecarScopes 計(jì)算邏輯后移至push階段,按需計(jì)算即沒必要計(jì)算所有Sidecar,只需要根據(jù)鏈接的proxy進(jìn)行計(jì)算即可,通過上述的優(yōu)化,可以做以下針對(duì)性調(diào)整:
- 如果集群內(nèi)服務(wù)較多,為每一個(gè)應(yīng)用創(chuàng)建一個(gè)sidecar,防止所有服務(wù)信息推送給envoy,導(dǎo)致envoy OOM。
 - 在上述優(yōu)化之后,InitContext的處理耗時(shí)可以從P99 30s下降到P99 5s左右,此時(shí),配置推送效率得到5倍左右的提升,那么 setProxyState 處的耗時(shí),將會(huì)被放大,CPU的使用率將會(huì)成倍增長(zhǎng),可以通過下述配置進(jìn)行優(yōu)化。
 
2.2.2 Pilot配置優(yōu)化
啟用 XDS 增量推送
通過給 istiod 配置 PILOT_ENABLE_EDS_DEBOUNCE環(huán)境變量,我們啟用 istiod 的增量推送而無需等待full push。
減少推送量
將 istiod 的 PILOT_FILTER_GATEWAY_CLUSTER_CONFIG 環(huán)境變量配置為 “true”,這樣 Istio 將僅推送 Gateway 所需的服務(wù)信息,這個(gè)配置將極大的減少每次推送的量。開啟這個(gè)特性之后,集群內(nèi)的 istiod 每次向 Gateway 推送的服務(wù)信息降低90%。
關(guān)閉Headless
將 istiod 的 PILOT_ENABLE_HEADLESS_SERVICE_POD_LISTENERS環(huán)境變量配置為 “false”,因?yàn)閔eadless svc對(duì)應(yīng)的endpoints發(fā)生了變化,會(huì)觸發(fā)full push的行為。
提高吞吐
默認(rèn)情況下,單個(gè) istiod 的推送并發(fā)數(shù)只有 100,在較大的集群內(nèi),可能會(huì)導(dǎo)致配置生效的延遲。istiod 環(huán)境變量 PILOT_PUSH_THROTTLE 可以配置這個(gè)并發(fā)數(shù)。建議根據(jù)集群規(guī)模進(jìn)行配置。
避免頻發(fā)推送
PILOT_DEBOUNCE_AFTER 與 PILOT_DEBOUNCE_MAX 是配置 istiod 去抖動(dòng)的兩個(gè)參數(shù)。
默認(rèn)配置是 100ms 與 10s ,這也就意味著,當(dāng)集群中有任何事件發(fā)生時(shí),Istio 會(huì)等待 100ms,如果開啟EDS,則增量推送不會(huì)等待。
若 100ms 內(nèi)無任何事件進(jìn)入,Istio 會(huì)立即觸發(fā)推送。否則 Istio 將會(huì)等待另一個(gè) 100ms,重復(fù)這一操作,直到總共等待的時(shí)間達(dá)到 10s 時(shí),會(huì)強(qiáng)制觸發(fā)推送。實(shí)踐中可以適當(dāng)調(diào)整這兩個(gè)值以匹配集群規(guī)模和實(shí)際應(yīng)用。攜程內(nèi)部調(diào)高 PILOT_DEBOUNCE_AFTER 到 10s,以避免頻繁推送對(duì)性能產(chǎn)生影響,也能夠避免極端情況下推送不及時(shí)導(dǎo)致的 503 問題。
三、Service Mesh未來展望
控制面的重心在于解決規(guī)?;瘑栴},后續(xù)控制面將會(huì)在下述領(lǐng)域深入探索:
- 控制?去除對(duì)k8s的資源的依賴,推送耗時(shí)下降到秒級(jí)別,滿?更?規(guī)模的接?
 - 使?NDS實(shí)現(xiàn)DNS解析功能,避免search域多次查詢,提升Mesh的可?性
 
團(tuán)隊(duì)將與社區(qū)深度合作,針對(duì)控制面,密切關(guān)注增量推送等特性,后續(xù)將優(yōu)先實(shí)現(xiàn)控制面穩(wěn)定性增強(qiáng),如下述功能:
- 連接限流:通過限流功能,降低大量Sidecar同時(shí)連接同一個(gè) Pilot 實(shí)例的風(fēng)險(xiǎn),減少服務(wù)風(fēng)暴發(fā)生的機(jī)率。
 - 熔斷:基于生產(chǎn)場(chǎng)景的壓測(cè)數(shù)據(jù),測(cè)算出單實(shí)例 Pilot 可服務(wù)的 Sidecar 上限,超過上限值后,新連接會(huì)被Pilot 拒絕。
 
Service Mesh作為云原生領(lǐng)域下一代微服務(wù)技術(shù),經(jīng)過 2 年多摸索與演進(jìn),攜程完成了多語言、多場(chǎng)景的業(yè)務(wù)落地, 實(shí)際論證了Service Mesh在流量管控、系統(tǒng)擴(kuò)展性的優(yōu)勢(shì),具有下沉服務(wù)治理能力到基礎(chǔ)設(shè)施層,高度解耦中間件與業(yè)務(wù)系統(tǒng)的可行性。
后續(xù),攜程將在總結(jié)前期非核心應(yīng)用Service Mesh化改造的基礎(chǔ)上,逐步推進(jìn)核心應(yīng)用的落地,同步打磨完善平臺(tái)能力,全面提升穩(wěn)定性,為行業(yè)落地Service Mesh提供最佳實(shí)踐和相關(guān)借鑒。















 
 
 
















 
 
 
 