在騰訊,已經有很多產品已使用或者正在嘗試使用istio來作為其微服務治理的基礎平台。不過在使用istio時,也有一些對通信性能要求較高的業務會對istio的性能有一些擔憂。由於envoy sidecar的引入,使兩個微服務之間的通信路徑變長,導致服務延時受到了一些影響,istio社區一直以來也有這方面的聲音。基於這類抱怨,我們希望能夠對這一通信過程進行優化,以更好的滿足更多客戶的需求。
首先,我們看一下istio數據面的通信模型,來分析一下為什么會對延時有這么大的影響。可以看到,相比於服務之間直接通信,在引入istio 之后,通信路徑會有明顯增加,主要包括多出了兩次本地進程之間的tcp連接通信和用戶態網絡代理envoy對數據的處理。所以我們的優化也分為了兩部分進行。
內核態轉發優化
那么對於本地進程之間的通信優化,我們能做些什么呢?
其實在開源社區已經有了這方面的探索了。istio官方社區在2019年1月的時候已經有了這方面討論,在文檔里面提到了使用ebpf的技術來做socket轉發的數據代理,使數據在socket層進行轉發,而不需要再往下經過更底層的TCP/IP協議棧的一個處理,從而減少它在數據鏈路上的通信鏈路長度。
另外,網絡開源項目cilium也在這方面有一個比較深入的實踐,同樣也是使用了ebpf的技術。不過在cilium中本地網絡加速只是其中的一個模塊,沒有作為一個獨立的服務進行開發實踐,在騰訊雲內部沒法直接使用,這也促使了我們開發一個無依賴的解決方案。
當然在初期的時候,我們也對ebpf的技術進行了一個驗證,從驗證結果中可以看到,在使用了ebpf的技術之后,它的延時大概有20%到30%的提升,說明ebpf的技術應用在本地通訊上還是有一定優化能力的。
簡單介紹一下ebpf,看一下它是怎么做到加速本地通訊的。首先ebpf可以看作是一個運行在內核里面的虛擬機,用戶編寫的ebpf程序可以被加載到內核里面進行執行。內核的一個verify組件會保證用戶的ebpf程序不會引發內核的crash,也就是可以保證用戶的ebpf程序是安全的。目前ebpf程序主要用來追蹤內核的函數調用以及安全等方面。下圖可以看到,ebpf可以用在很多內核子系統當中做很多的調用追蹤。
另外一個比較重要的功能,就是我們在性能優化的時候使用到的在網絡上的一個能力,也就是下面提到的sockhash。sockhash本身是一個ebpf特殊的一個kv存儲結構,主要被用作內核的一個socket層的代理。它的key是用戶自定義的,而value是比較特殊的,它存儲的value是內核里面一個socket對象。存儲在sockhash中的socket在發送數據的時候,如果能夠通過我們掛在sockhash當中的一個ebpf當中的程序找到接收方的socket,那么內核就可以幫助我們把發送端的數據直接拷貝到接收端socket的一個接收隊列當中,從而可以跳過數據在更底層的處理,比如TCP/IP協議棧的處理。
在sidecar中,socket是怎樣被識別並存儲在sockhash當中來完成一個數據拷貝的呢?我們需要分析一下數據鏈的本地通訊的流量特征。
首先從ingress來講,ingress端的通信會比較簡單一點,都是一個本地地址的通信。ingress端的envoy進程和用戶服務進程之間通信,它的原地址和目的地址剛好是一一對應的。所以我們可以利用這個地址的四元組構造它的key,把它存儲到sockhash當中。在發送數據的時候,根據這個地址信息反向構造這個key,從sockhash當中拿到接收端的socket返回給內核,這樣內核就可以幫我們將這個數據直接拷貝給接收端的socket。
egress會稍微復雜一點,在一個egress端服務程序對外發出的請求被iptables規則重定向到了envoy監聽的一個15001的端口。在這里,發起方的源地址和接收方的目的地址是一一對應的,但是發起方的目的地址和接收端的源地址有了一個變化,主要是由於iptables對地址有一個重寫。所以我們在存儲到sockhash中的時候,需要對這部分信息進行一個處理。由於istio的特殊性,直接可以把它改寫成envoy所監聽的一個本地服務地址。這樣再存儲到sockhash當中,它們的地址信息還是可以反向一一對應的。所以在查找的時候,還是可以根據一端的socket地址信息查找到另一端的socket,達到數據拷貝的目的。
經過對ebpf加速原理的分析,我們開發出來一個ebpf的插件,這個插件可以不依賴於集群本身的網絡模式,使用daemonset方式部署到k8s集群的各個節點上。其中的通信效果如下圖所示,本地進程的一個通信在socket層直接被ebpf攔截以后,就不會再往下發送到TCPIP協議棧了,直接在socket層就進行了一個數據拷貝,減少了數據鏈路上的一個處理流程
下面是對效果的一個測試,從整體來看,它的延時有大概5到8%的延時提升,其實提升的幅度不是很大,主要原因其實在整個通信的流程當中,內核態的一個處理占整個通信處理的時間,延時其實是比較少的一部分,所以它的提升不是特別明顯。
另外ebpf還有一些缺陷,比如它對內核版本的要求是在4.18版本之后才有sockhash這個特性。另外sockhash本身還有一些bug,我們在測試當中也發現了一些bug,並且把它提交到社區進行解決。
Envoy 性能研究與優化
前面介紹了對istio數據面對流量在內核的處理所進行的一些優化。在istio數據面的性能問題上,社區注意到比較多的是在內核態有一個明顯的轉發流程比較長的問題,因此提出了使用eBPF進行優化的方案,但是在Envoy上面沒有太多的聲音。雖然Envoy本身是一個高性能的網絡代理,但我們還是無法確認Envoy本身的損耗是否對性能造成了影響,所以我們就兵分兩路,同時在Envoy上面進行了一些研究。
首先什么是Envoy?Envoy是為分布式環境而生的高性能網絡代理,可以說基本上是作為服務網格的通用數據平面被設計出來的。Envoy提供不同層級的filter架構,如listenerFilter、networkFilter以及HTTPFilter,這使envoy具有非常好的可擴展性。Envoy還具有很好的可觀察性,內置有stats、tracing、logging這些子系統,可以讓我們更容易地對系統進行監控。
進行istio數據面優化的時候,我們面對的第一個問題是Envoy在istio數據面中給消息轉發增加了多少延時?Envoy本身提供的內置指標是很難反映Envoy本身的性能。因此,我們通過修改Envoy源碼,在Envoy處理消息的開始與結束的位置進行打點,記錄時間戳,可以獲得Envoy處理消息的延時數據。Envoy是多線程架構,在打點時我們考慮了性能和線程安全問題:如何高效而又准確地記錄所有消息的處理延時,以方便后續的進行分析?這是通過如下方法做到的:
a. 給壓測消息分配唯一數字ID;
b. 在Envoy中預分配一塊內存用於保存打點數據,其數據類型是一個結構體數組,每個元素都是同一條消息的打點數據;
c. 提取消息中的數字ID當作時間戳記錄的下標,將時間戳記錄到預分配的內存的固定位置。通過這種方式,我們安全高效地實現了Envoy內的打點記錄(缺點是需要修改Envoy以及壓測工具)。
經過離線分析,我們發現了Envoy內消息處理延時的一些分布特征。左圖是對Envoy處理延時與消息數量分布圖,橫軸是延時,縱軸是消息數量。可以看到在高延時部份,消息數量有異常增加的現象,這不是典型的冪率分布。進一步對高延時部份進行分析后發現,這些高延時消息均勻地分布於整個測試期間(如上圖右所示)。 根據我們的經驗,這通常是Envoy內部轉發消息的worker在周期性的執行一些消耗CPU的任務,導致有一部分消息沒辦法及時轉發而引起的。
深入研究Envoy在istio數據面的功能實現后,我們發現mixer遙測可能是導致這個問題的根本原因。Mixer是istio老版本(1.4及以前)實現遙測和策略檢查功能的一個組件。在數據面的Envoy中,Mixer會提取所有消息的屬性,然后批量壓縮上報到mixer server,而屬性的提取和壓縮是一個高CPU的消耗的操作,這會引起延時數據分析中得到的結果:高延時消息轉發異常增多。
在確定了原因之后,我們對Envoy的架構作了一些改進,給它增加了執行非關鍵任務的AsyncWorker線程,稱為異步任務線程。通過把遙測的邏輯拆分出來放到了AsyncWorker線程中去執行,就解決了worker線程被阻塞的問題,進而可以降低envoy轉發消息的延時。
進行架構優化之后,我們也做了對比測試,上圖左測是延時與消息數量圖,可以看到它高延時部分得到明顯的改善。上圖右可以看出Envoy整體的延時降低了40%以上。
優化Envoy架構給我們帶來了第一手的經驗,首先CPU是影響Istio數據面性能的關鍵資源,它的瓶頸主要出現在CPU上面,而不是網絡IO操作。第二,我們對Envoy進行架構優化,可以降低延時,但是沒有解決根本問題,因為CPU的使用沒有降低,只是遙測邏輯轉移到另外的線程中執行,降低Envoy轉發消息的延時。優化CPU使用率才是數據面優化的核心。第三點,Envoy的不同組件當中,mixer消化掉了30%左右的CPU,而遙測是mixer的核心功能,因此后續遙測優化就變成了優化的重要方向。
怎么進行遙測優化呢?其實mixer實現遙測是非常復雜的一套架構,使用Istio mixer遙測的人都深有體會,幸好istio新版本中,不止對istio的控制面作了大的調整,在數據面mixer也同樣被移除了,意味着Envoy中高消耗的遙測就不會存在了,我們是基於istio在做內部的service mesh,從社區得到這個消息之后,我們也快速跟進,引入適配新的架構。
沒有Mixer之后,遙測是如何實現的。Envoy提供了使用wasm對其進行擴展的方式,以保持架構的靈活性。而istio社區基於Wasm的擴展開發了一個stats extension擴展,實現了一個新的遙測方案。與mixer遙測相比,這個stats extension不再上報全量數據到mixer server,只是在Envoy內的stats子系統中生成遙測指標。遙測系統拉取Envoy的指標,就可以獲得整個遙測數據,會大大降低遙測在數據面的性能消耗。
然后我們對istio 1.5使用Wasm的遙測,做了一個性能的測試。發現整個Envoy代理在同樣測試條件下,它的CPU降低10%,而使用mixer的遙測其實占用了30%的CPU,里面大部分邏輯是在執行遙測。按我們的理解,Envoy至少應該有20%的CPU下降,但是實際效果只有10%左右,這是為什么呢?
新架構下我們遇到了新的問題。我們對新架構進行了一些實現原理和技術細節上的分析,發現Envoy使用Wasm的擴展方式,雖然帶來了靈活性和可擴展性,但是對性能有一定的影響。
首先,Wasm擴展機制跟Envoy host環境是通過內存拷貝的方式進行通信,這是Wasm虛擬機的隔離性機制決定的。Envoy為了保持架構靈活性的同時保證性能,使設計了一個非Wasm虛擬機運行擴展(如stats extension)的模式,即NullVM模式,它是一個假的Wasm虛擬機,實際上運行的擴展還是被編譯在Envoy內部,但它也逃離不掉Wasm架構帶來的內存拷貝影響。
其次,在實現extension與Envoy的通信時,一個屬性的獲取要經過多次的內存拷貝,是一個非常復雜的過程。如上圖所示,獲取request.url這個屬性需要在Envoy的內存和Wasm虛擬機內存之間進行一個復雜的拷貝操作,這種方式的消耗遠大於通過引用或指針提取屬性。
第三,在實現遙測的時候,有大量的屬性需要獲取,通常有十幾二十個屬性,因此Wasm擴展帶來的總體額外損耗非常可觀。
另外,Wasm實現的遙測功能還需要另外一個叫做metadata_exchange擴展的支持。metadata_exchange用來獲得調用對端的一些節點信息,而metadata_exchange擴展運行在另外一個虛擬機當中,通過Envoy的Filter state機制與stats 擴展進行通信,進一步增加了遙測的額外消耗。
那么如何去優化呢?簡單對Wasm插件優化是沒有太大幫助,因為它的底層Wasm機制已經決定了它有不少的性能損耗,所以我們就開發了一個新的遙測插件tstats。
tstats使用Envoy原生的擴展方式開發。在tstats擴展內部,實現了遙測和metadata_exchange的結合,消除了Wasm帶來的性能弊端。Tstats遙測與社區遙測兼容,生成相同的指標,tstats基於istio控制面的EnvoyFilter CRD進行部署,用戶可以平滑升級,當用戶發現tstats的功能沒有滿足需求或者出現一些問題時,也可以切換使用到社區提供的遙測擴展。
在tstats擴展還優化了遙測指標的計算過程。在計算指標的時候有許多維度信息需要填充,(目前大指標有二十幾個維度的填充),這其實是一個比較復雜的操作,其實,有很多指標的維度都是節點信息,就是發起服務調用的客戶端和服務端的一些信息,如服務名、版本等等。其實我們可以將它進行一些緩存,加速這些指標的計算。
經過優化之后,對比tstats遙測和官方的基於Wasm的遙測的性能,我們發現CPU降低了10到20%,相對於老版本的mixer來說降低了20%以上,符合了我們對envoy性能調研的一個預期。上圖右可以看到在延時上有一個明顯的降低,即使在P99在不同的QPS下,也會有20%到40%的總體降低(這個延時是使用echo service做End-to-End壓測得到的)。
使用火焰圖重新觀察一下Envoy內部的CPU使用分布,我們發現tstats遙測插件占用CPU的比例明顯更少,而使用wasm的遙測插件有一個明顯的CPU占用來實現遙測,這也證明了tstats優化是有效果的。
總結
前面我們分享了在優化istio數據面過程當中,在內核態和Envoy內探索的一些經驗。這是我們優化的主要內容,當然還有一些其它的優化點,例如:
- 使用XDP進行加速,因為由於istio proxy的引入,Pod和Pod之間的訪問實際上是不需要經過主機上的iptables規則處理。基於這一點,我們可以利用XDP的快速轉發能力直接將包發送到對應的網卡,完成快速轉發。
- 鏈路跟蹤在Envoy中消耗的CPU也比較可觀,即使在1%的采樣率下也消耗了8%的CPU,是可以進行優化的,所以我們也在做這部分的工作。
- Envoy內部有非常多的內置統計,Istio的一些指標與Envoy內置的指標有一部分重復,可以考慮進行一些裁剪優化,或者增加一些特性開關,當不需要使用的時候對它進行關閉。
總體來說,Istio數據面性能的損耗分布在各個環節,並不是單獨的內核態消息轉發或者用戶態Envoy就消耗特別多。在使用Mixer架構的Istio版本中,Envoy內一個明顯的性能熱點, mixer遙測,這也在版本迭代中逐步解決了。
最后,在進行Istio數據面優化的時候需要綜合考慮各個環節,這也是我們目前總體上對Istio數據面性能的一個認識,通過這次分享,希望社區和大家都會在更注重Istio數據面的性能,幫助ServiceMesh更好地落地。騰訊雲基於Istio提供了雲上ServiceMesh產品TCM,大家有興趣可以來體驗。
問題
-
怎么判斷項目需要使用服務網格?
服務網格解決的最直接的場景就是你的服務需要進行微服務治理,但是你們之前可能有多個技術棧,沒有一個統一的技術框架,比如沒有使用SpringCloud等。缺少微服務治理能力,但是又想最低成本獲得鏈路跟蹤、監控、流量管理、熔斷等這樣的能力,這個時候可以使用服務網格實現。
-
這次優化有沒有考慮到回歸到社區?
其實我們也考慮過這個問題,我們在進行mixer優化的時候,當時考慮到需要對Envoy做比較多的改動,並且了解到社區規划從架構中去掉mixer,所以這個並沒有回歸到社區,異步任務線程架構的方案目前保留在內部。對於第二點開發的tstats擴展,它的功能和社區的遙測是一樣的,如果提交到社區我們覺得功能會有重疊,所以沒有提交給社區。
-
服務網格數據調優給現在騰訊的業務帶來了哪些改變?
我想這里主要還是可觀測性上面吧,之前很多的服務和開發,他們的監控和調用面的上面做得都是差強人意的,但是他們在業務的壓力之下,其實是沒有很完善的方式、沒有很大的動力快速實現這些服務治理的功能,使用istio之后,有不少團隊都獲得了這樣的能力,他們給我們的反饋都是比較好的。
-
在減少延遲方面,騰訊做了哪些調整,服務網格現在是否已經成熟,對開發者是否友好?
在延遲方面,我們對性能的主要探索就是今天分享的內容,我們最開始就注意到延時比較高,然后在內核做了相應的優化,並且研究了在Envoy內為什么會有這些延遲。所以我們得出的結論,CPU是核心的資源,需要盡量降低數據面Proxy代理對CPU的使用,這是我們做所有優化最核心的出發點,當CPU降下來,延時就會降低。服務網格現在是否已經成熟。我覺得不同的人有不同答案,因為對一些團隊,目前他們使用服務網格使用得很好的,因為他們有多個技術棧,沒有統一的框架。他們用了之后,獲得這些流量的管理和監控等等能力,其實已經滿足了他們的需求。但是對一些成熟的比較大的服務,數據面性能上面可能會有一些影響,這個需要相應的團隊進行仔細的評估才能決定,並沒辦法說它一定就是能在任何場景下可以直接替換現有的各個團隊的服務治理的方式。
-
為什么不直接考慮1.6?
因為我們做產品化的時候,周期還是比較長的。istio社區發版本的速度還是比較快的,我們還在做1.5產品化的時候,可能做着做着,istio1.6版本就發出來了,所以我們也在不停更新跟迭代,一直在跟隨社區,目前主要還是在1.5版本上。其實我們在1.4的時候就開始做現在的產品了。
-
公司在引入多種雲原生架構,包括SpringCloud和Dubbo,作為運維,有必要用Istio做服務治理嗎?另外SpringCloud和Dubbo這種架構遷移到Istio,如何調整?
目前SpringCloud和Dubbo都有不錯的服務治理功能,如果沒有非常緊迫的需求,比如你們又需要引入新的服務並且用別的語言實現,我覺得繼續使用這樣的框架,可能引入istio沒有太大的優勢。但是如果考慮進行更大規模的服務治理,包括融合SpringCloud和Dubbo,則可以考慮使用istio進行一個合並的,但是這個落地會比較復雜。那么SpringCloud和Dubbo遷移到Istio如何調整?目前最復雜的就是他們的服務注冊機制不一樣,服務注冊模型不一樣。我們之前內部也有在預研如何提供一個統一的服務注冊模型,以綜合Istio和其它技術框架如SpringCloud的服務注冊和服務發現,以及SpringCloud如何遷移進來。這個比較復雜,需要對SDK做一些改動,我覺得可以下來再進行交流。
【騰訊雲原生】雲說新品、雲研新術、雲游新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!