楊怡濱 楊 偉 于新容 丘偉森
(廣東省輕工業高級技工學校信息工程系 廣東 廣州 510315)
服務網格[1]是致力于解決服務間通信的基礎設施層,負責在構成復雜應用系統的微服務間靈活、高效和可靠地傳遞請求,用于控制和監視微服務應用的內部、服務到服務的通信。
服務網格由數據面和控制面組成,其中控制面用于集中設置相關策略進行服務間交互的流量監控與控制策略實施,而數據面則由一組輕量級網絡代理(簡稱邊車代理)構成。邊車代理是一個輔助進程,它與主應用程序一起運行,并為其提供額外的功能。每個服務都配備了一個邊車代理,它們與微服務一起部署用來實現(即邊車部署模式)服務間的交互。
服務網格將服務間通信從底層的基礎設施中分離出來使得服務能夠被監控、托管和控制,同時為服務運行時提供統一的、應用層面的可見性和可控性,因此有效解決了大規模微服務服務治理問題。
目前服務網格技術仍處于發展階段,性能是服務網格面臨的核心問題和挑戰[2]。服務網格(尤其是邊車代理)的加入會增加請求調用鏈路的長度,因此必然會帶來性能的損耗。眾所周知,服務架構多用于分布式互聯網系統應用中,而來自客戶端的高并發訪問請求(如車票預訂、搶紅包、雙十一購物節等場景)十分常見,在使用服務網格后,并發壓力從應用層服務轉移到服務網格層面。因此,如何使服務網格具備高性能的、異步無阻塞的通信交互能力十分重要。本文分析發現,影響服務網格整體性能的主要因素如下:(1) 服務網格數據面中的邊車代理負責服務間通信,在代理請求、向外轉發請求和回傳響應三個關鍵步驟涉及大量網絡IO和計算,在高并發場景下使用大量線程資源,頻繁的線程切換會嚴重影響并發性能。(2) 服務間及邊車代理間交互通常基于HTTP協議,而傳統的HTTP協議具有固定的消息格式,其中可能包含冗余的數據,其次HTTP協議基于TCP協議,因此每次請求建立連接時都經歷三次握手,在高并發的場景下可能會造成明顯的延遲。
針對上述問題,本文提出了面向服務網格的性能優化方法,首先基于Reactor模式[3],同時結合線程和協程[4]兩者的計算優勢,設計了一種具有高并發處理能力的計算模型,并將其用于邊車代理的設計實現;其次基于HTTP協議設計了一種輕量、高效的應用層交互協議,用以減少服務邊車代理間通信時傳輸的數據量,同時實現了基于零拷貝技術[5]的協議消息編解碼算法,可以顯著提高通信能力。
本文基于上述技術設計實現了一個原型系統,并通過實驗與當前主流的服務網格系統Linkerd和Envoy進行了對比。實驗分別從并發性能和通信性能兩方面進行分析比較,結果表明本文提出的優化方法使得服務網格系統在減少響應時間和網絡傳輸數據量兩方面均有顯著的性能提升。
服務網格是近兩年興起的技術,其相關研究目前較少,但是已經得到了工業界的廣泛關注和應用。Linkerd和Envoy是目前典型的服務網格系統,Linkerd從2016年到2018年圍繞性能優化發布了1.0.x、1.1.x、1.2.x、1.3.x、1.4.x、1.5.x、1.6.x等版本,嘗試使用最新版本的Finagle框架優化、使用GraalVM提高性能、通過Open J9 JVM的支持降低40%的內存占用并大幅降低長尾延遲,目前GraalVM性能優化方案失敗,僅存Open J9 JVM的優化版本,其測試版本還在繼續發行。Envoy在2017年到2018年發布了1.2.x、1.3.x、1.4.x、1.5.x、1.6.x、1.7.x、1.8.x等版本,從多線程加非阻塞異步IO計算模型、通信協議優化和熱重啟等方面進行了優化設計。
國內自2017年底大規模開展高性能服務網格技術的研發,其中:華為用Go語言在路由管理、多協議支持方面做優化,自行開發了Mesher;螞蟻金服從IO、協議、調度策略方面對服務網格進行了優化并開發了SOFAMesh;新浪開發的WeiboMesh在通信方面提供HTTP Mesh方案,支持HTTP與RPC服務之間的交互。但是,服務網格的加入增加了請求調用鏈路的長度,必然帶來性能的損耗,例如:阿里巴巴對基于Envoy自主開發的Dubbo Mesh進行測試,發現Dubbo Mesh的每一次請求轉發會造成1.5 ms的延時。
與上述工作不同,本文主要從并發處理模型和服務網格數據傳輸兩方面提出性能優化的方法。
基于線程的并發和事件驅動是并發處理的兩種基本方法。迄今為止,事件驅動的并發方法因其更高的性能和更好的可伸縮性而得到更為廣泛的應用。Reactor模式是當前具有代表性的事件驅動的并發處理模式之一,常用于具有高并發需求的客戶端-服務器系統的設計和實現中,許多流行的開源框架和服務器系統均基于Reactor模式,如Netty、Redis和Node.js等。
Reactor模式[3]稱為反應器模式,是一種為處理并發服務請求,并將請求提交到一個或者多個服務處理程序的事件驅動并發處理設計模式。當客戶端請求抵達后,服務處理程序使用多路分配策略,由一個非阻塞線程來接收所有的請求,然后派發這些請求至相關的工作線程進行處理。Reactor模式主要包含如下四部分內容。
(1) 初始事件分發器(Initialization Dispatcher)用于管理事件處理器(Event Handler),負責定義注冊、移除事件處理器等。當服務請求到達時,它根據事件發生的Handle將其分發給對應的事件處理器進行處理。
(2) 同步(多路)事件分離器(Synchronous Event Demultiplexer)無限循環等待新事件的到來,一旦發現有新的事件到來,就會通知初始事件分發器去調取特定的事件處理器。
(3) 系統處理程序(Handles)是操作系統中的句柄,是對資源在操作系統層面上的一種抽象,它可以是打開的文件、一個連接(Socket)、時鐘等。由于Reactor模式一般使用在網絡編程中,因而這里一般指Socket Handle,即一個網絡連接(Connection Channel)注冊到同步(多路)事件分離器中,以監聽Handle中發生的事件,包括網絡連接事件、讀/寫和關閉事件等。
(4) 事件處理器(Event Handler)用來定義事件處理方法,以供初始事件分發器回調使用。
對于高并發系統,常會使用Reactor模式,其代替了常用的多線程處理方式,節省了系統的資源,提高了系統的吞吐量。由于服務網格系統作為服務間交互的基礎設施層常常面臨服務請求與響應的高并發場景,因此本文選擇基于Reactor模式進行服務網格中邊車代理并發處理模型的設計。本文對Reactor模式進行了擴展,結合線程和協程的各自優勢,實現由Manager、Worker和Collaborator三類角色協同的高并發處理機制。
服務網格的典型工作模式如圖1所示。以單方面的Microservice A的視角為例:Microservice A的服務網格組件邊車(Mesh A)代理Microservice A對Microservice B的網絡通信,Mesh A一方面需要維護與Microservice A的連接,另一方面需要將Microservice A的請求數據轉發給Microservice B(Microservice B的網絡通信由Mesh B代理),然后Mesh A收到響應并回傳給Microservice A。

圖1 服務網格工作模式
在Microservice A通信的過程中,Mesh A充當了類似“Server”與“Client”的角色,其中的計算過程可以分解成代理請求、向外轉發請求和回傳響應三個關鍵步驟。由于Reactor模型通常用于服務器端的設計,而每個服務網格的組件邊車代理在圖1所示的工作模式中都會承擔“Server”的角色,基于這一觀察分析,本文提出了基于Reactor模式的并發計算模型,用于設計構成服務網格數據面的邊車代理。
如圖2所示,該模型分為Manager、Worker和Collaborator三種角色。Manager以線程作為計算實體運行,用于維護服務的連接及分發連接的網絡事件給Worker;Worker同樣以線程作為計算實體運行,Worker執行相關數據計算的任務,然后通過Collaborator向外轉發請求;Collaborator以協程作為計算實體運行,轉發請求并等待、計算、處理和回傳響應。由圖2可以看出Manager和Worker基于Reactor模式,而Collaborator則是在Reactor模式上的進一步擴展。

圖2 基于Reactor的高并發計算模型
Manager和Worker基于Reactor模式設計實現,傳統的順序編程采用每條指令依次執行的方式,難以滿足高性能的需求。Reactor模式是基于數據流和變化傳遞的聲明式的編程范式,用消息發送的事件流驅動機制取代傳統的順序執行機制[6]。Manager和Worker服務的請求數據作為數據流,Manager建立、維護相關連接后不會阻塞消息處理,Manager會監聽各種網絡事件并分發給相應的Worker進行處理。
服務網格啟動時,綁定操作系統的某個端口后,首先將套接字(Socket)注冊到Manager線程的選擇器(Selector)上。Manager負責維護連接上下文的一致性,通過Selector監聽客戶端的TCP連接請求,并將消息的數據流事件循環(EventLoop)調度給相應的Worker進行處理。Worker負責消息的讀取、解碼、編碼和發送,Worker采用串行化設計,1個Worker線程可以同時處理N條鏈路連接,1條鏈路只對應1個Worker線程,通過串行化設計有效防止并發操作問題。
隨后,Worker和Collaborator協作,應用于服務網格向外轉發請求和回傳響應的場景。Worker通過scheduler調度器調度Collaborator執行任務,Collaborator協程執行完任務后會動態從其他的任務隊列偷取任務執行。Collaborator負責請求轉發和響應回傳等任務,ClientChannel是轉發請求時與目標服務的連接管道,Messenger執行ClientChannel的讀、寫、解碼、編碼、轉發等,ServerChannel是與代理服務的連接,Messenger回傳響應時,將數據寫入ServerChannel。與Worker不同,1個Collaborator處理1條鏈路連接,1個鏈路對應1個Collaborator協程。Collaborator由Worker觸發scheduler調度,Worker的事件循環組可包含多個事件循環,每個事件循環包含1個選擇器和1個事件循環線程,每個事件循環線程通過事件機制向scheduler調度器調度Collaborator。
Collaborator基于協程實現。對比線程,協程有以下優勢:(1) 更好地利用CPU資源,沒有類似線程調度的上下文切換;(2) 更好地利用內存資源,用戶只需要分配合適的空間即可。因此能夠避免頻繁的線程切換和由此導致的因CPU緩存行失效引起的性能下降問題。
為了減少服務網格中代理間傳輸的數據量,減輕網絡負載,本文基于HTTP協議進行了通信協議的定制化,設計了輕量化的協議和相應的編解碼算法,能夠有效降低服務代理交互的通信數據量,提高編解碼速率。
如圖3所示,定制化協議的結構由固定長度的消息頭Head(8字節)和不定長度的消息體組成,具體組成如下:Magic Number:表示是否該協議的數據包;Serial ID:表示標志請求的序列號;Event Type:表示事件類型,1表示心跳消息,0表示不是心跳消息;Response:返回數據,0表示服務端可以不作應答,1表示服務端必須應答消息;Message Type:表示請求或響應的消息狀態;Response Code:請求響應狀態碼,200表示成功,400表示失敗,每種狀態碼對應相關原因;Message Length:表示消息長度;Message Body:表示消息數據。

圖3 定制化協議數據結構
本文設計的編解碼算法主要解決網絡傳輸過程中出現的半包/粘包問題,同時基于零拷貝技術有效減少操作系統內核態切換用戶態過程的性能損失。
如圖4所示,客戶端發送Msg1和Msg2兩個數據包給服務端,網絡通信過程中會出現以下問題:(1) 無半包、無粘包,即服務端依次收到兩個獨立完整的數據包;(2) 半包,即服務端收到一個數據包,數據包只包含了Msg1的一部分;(3) 粘包,即服務端收到一個數據包,數據包包含了兩個請求的數據。

圖4 半包和粘包
基于上述情況,本文設計了一個基于計數的可彈性伸縮的鏈式數據結構BufferLinkList,用以緩存網絡交互數據,并進行編解碼。BufferLinkList底層是循環鏈表,由Buffer塊構成,Buffer塊封裝了直接內存,可以指定大小,默認為64字節,RefCnt表示Buffer的引用計數,通過引用計數,系統會自動進行垃圾回收。BufferLinkList能夠彈性伸縮,如果數據過多,BufferLinkList會自動擴容,如果數據過少,會自動縮容。基于BufferLinkList的零拷貝體現在以下方面:(1) 在直接內存里面分配空間,而不是在堆內存中分配;(2) BufferLink-List會自動增減Buffer塊,對上層提供統一讀寫接口,避免了數據的拷貝。BufferLinkList的數據結構如圖5所示。

圖5 BufferLinkList數據結構
編碼時,首先計算數據包的大??;然后根據協議數據結構編碼并存儲到BufferLinkList中;最后通知操作系統發送網絡數據。編碼算法如算法1所示。
算法1編碼算法
輸入:data。
輸出:bufferLinkList。
1. size←caculateSize(data)
//計算數據包大小
2. bufferLinkList←ButfferLinkListPool.getBufferLinkList(size)
//申請鏈表
3. header←HeaderTemplate.getHeader()
//從模板初始化頭部數據
4. header.setMagic(MAGIC)
//用戶自定義配置
5. header.setld(ID)
6. header.setlsHeartBeat(false)
7. head.setLength(data,length)
8. ……
9. bufferLinkList.write(head,data)
//寫入數據
10. bufferLinkList.setRefCnt(1)
11.returnbufferLinkList
EndFunction
解碼時,首先用BufferLinkList緩存操作系統收到的網絡數據;然后根據已緩存數據的大小判斷是否滿足Head解碼階段,如果滿足則進入Head解碼,如果不滿足則繼續緩存;完成Head解碼后計算消息的長度并進入Body解碼狀態,如果網絡數據大于等于消息長度則執行完解碼過程,并對已解碼的數據重置引用計數,否則繼續緩存,等待下一個Body解碼狀態。解碼算法如算法2所示。
算法2解碼算法
輸入:bufferLinkList。
輸出:data。
1. decodeStat←State.Head
//初始化解碼狀態
2. length←0
3.whilebufferLinkList←receive_data()do
4.ifdecodeState==State.Headthen
//判斷解碼階段
5.ifbufferLinkList.size()<32then
6.continue
//數據不足,跳出
7.else
8. length←bufferLinkList.getDataBodyLength()
9. decodeState←State.Body
//進入Body解碼階段
10.ifbufferLinkList.size() -32>=lengththen
11.gotoGetData
12.else
13.continue
14.endif
15.endif
16.elseifdecodeState==State.Body
17.ifbufferLinkList.size() -32 18.continue //數據不足,跳出 19.else 20. GetData: data←bufferLinkList.getData(length) //獲取數據體 21. endIndex←bufferLinkL ist.startIndex+length+32 //應用計數重置 22. bufferLinkList.setRefCnt(startIndex,endIndex,0) 23. bufferLinkList.setStartIndex(endlndex) //指正清零 24.returndata 25.endif 26.endif 27.endwhile EndFunction 基于上述關鍵技術,本文基于Java 8設計實現了一個原型系統如圖6所示。整個系架構采用分層模型設計,包括核心層、計算層、應用層。 圖6 原型系統總體架構 1) 核心層:包括BufferLinkListPool和統一通信接口。BufferLinkListPool提供BufferLinkList,BufferLinkList是封裝了直接內存的鏈式結構,用于緩存網絡IO數據,支持基于計數的垃圾回收,并且彈性可伸縮;統一通信API層提供了基于BufferLinkList的通信相關的操作接口。 2) 計算層:提供了以線程封裝的Manager、Worker等計算資源及用協程封裝的Messenger計算資源,基于本文提出的并發處理模型提供高并發處理能力。 3) 應用層:實現了服務網格的核心服務,如:服務注冊與發現服務、協議與編解碼服務、路由選取服務等。 網絡數據進入服務網格后的處理流程包括以下步驟:(1) 網絡數據從物理網卡進入,操作系統陷入內核態處理;(2) 服務網格從BufferLinkListPool獲取BufferLinkList,并緩存IO數據;(3) 使用反應式編程的BWM計算模型的計算資源,Manager線程維護連接,并向Worker線程分發任務;(4) Worker線程通過統一通信API處理相關數據,如編解碼,然后分發給Collaborator協程,并重新編碼向外轉發;(5) Collaborator收到請求后回傳給代理服務。 為了評價本文方法的有效性,本節首先設計相關實驗,將原型系統(簡稱BWM)與目前主流服務網格系統Linkerd(1.6版本)和Envoy(1.8版本)在相同環境下進行性能測試。實驗環境基于2臺阿里云服務器搭建,每臺服務器配置信息如圖7所示,并根據實驗結果進行分析對比。 圖7 實驗環境設計 實驗由施壓方和受壓方組成,施壓方Benchmarker會產生三組高并發連接(128并發連接、256并發連接和512并發連接)向Cosumer服務進行壓測,Benchmarker生成隨機的字符串data并發送請求給Cosumer服務,Cosumer服務收到該請求后會通過服務網格將data發給Provider服務的服務網格,Provider服務的服務網格將data發給Provider服務,Provider服務計算data的HashCode后將HashCode(data)通過服務網格返回Cosumer服務的服務網格,Cosumer服務的服務網格將響應返回給Cosumer服務,Cosumer服務最后將結果返回給Benchmarker校驗并統計相關性能指標。 實驗時每一組壓力測試持續時間60 s,運行10次,去掉最好和最差的實驗數據,最終的實驗數據取平均值。實驗流程如下: (1) 每輪評測在受壓方中啟動五個服務(以Docker實例的形式啟動),一個ETCD服務作為注冊表、一個Consumer服務和三個Provider服務,每個服務都會綁定服務網格,Provider服務會Sleep(50 ms)模擬計算時間; (2) 使用另一臺獨立的服務器作為施壓方,分不同壓力場景對Consumer服務進行壓力測試,得到相關性能指標; (3) ETCD是注冊中心服務,用來存儲服務注冊信息;Provider是服務提供者,Consumer是服務消費者,Consumer消費Provider提供的服務。 為了盡可能模擬真實情況,每個服務實例所占用的系統資源各不相同,運行Consumer服務及其Service Mesh的實例(為了便于描述,下文將簡稱為Consumer實例,Provider實例類似)占用的系統資源是最多的,而三個Provider實例占用的系統資源總和與Consumer實例占用的系統資源是相同的,并且Provider實例按照small ∶medium ∶large=1 ∶2 ∶3的比例進行分配,分為Provider(small)實例、Provider(medium)實例和Provider(large)實例,服務實例的資源分配如表1所示。 表1 服務實例資源分配 在每個Consumer和Provider實例中,都存在一個服務網格實例,其在整個系統中起到了非常關鍵的作用。 (1) Consumer服務是基于Spring Cloud實現的Web應用,會發送請求給Provider服務,服務之間數據傳遞是通過服務網格進行的,服務網格的性能決定了系統的性能;(2) 任何一個Provider實例的性能都是小于Consumer實例的,服務實例的調度選取策略意義重大;(3) Provider實例最高支持200并發的連接,高并發下會產生回壓問題。 實驗使用wrk作為施壓方,wrk是一個基于事件機制的高性能HTTP壓力測試工具,能用很少的線程產生極高的訪問壓力。實驗統計性能測試指標如表2所示。 表2 性能測試指標 4.3.1并發性能分析 為了評價本文提出的并發模型對于提高服務網格并發性能的有效性,本文設計相關實驗與目前主流的服務網格系統Linkerd和Envoy進行比較,在相同實驗條件下,用wrk測試三者的性能表現。 實驗結果顯示本文原型系統和Envoy(C++實現)有非常相似的表現,兩者性能都明顯優于Linkerd。詳細結果如表3-表5所示,從延時分布上分析,并發連接從128并發連接上升到512并發連接的過程中,BWM和Envoy的延遲分布比Linkerd更穩定。具體來說,Linkerd的平均延遲從58.42 ms至117.66 ms,增加101.40%,在相比之下,BWM和Envoy的平均延遲從約52 ms增加到約72 ms,增加38.46%。Linkerd的延遲變化很大,以512并發連接實驗為例,Linkerd的50%的請求在80.44 ms內,99%的所有請求在522.44 ms內,相比之下,BWM和Envoy的變化僅從約67 ms(50%)到大約145 ms(99%)。 表3 128并發連接下的測試結果 表4 256并發連接下的測試結果 表5 512并發連接下的測試結果 可以看出BWM和Envoy的性能表現明顯優于Linkerd。最初,128并發連接下,三者性能差異很小,其中BWM的表現最為優秀,BWM和Envoy的差距不大,但是兩者都明顯優于Linkerd,Linkerd僅比其他兩個低約12%;256并發連接下,Linkerd、BWM和Envoy的QPS分別為3 005.75、4 559.11、4 583.77,512并發連接下,Linkerd、BWM和Envoy的QPS分別為5 085.25、7 015.28、7 146.75,差距隨著并發的增加而增加。需要注意的是,雖然BWM的性能和Envoy是非常相似的,但是最大響應時間(Max.RT)BWM均比Envoy小,考慮C++語言比Java存在的一定性能優勢,因此BWM的性能表現十分優秀。 4.3.2交互性能分析 為了評價本文設計的通信協議(BWMP)的有效性,本文在相同實驗條件下,分別測試使用HTTP協議通信和自定義協議通信下的性能表現。 實驗結果如表6所示,可見使用BWMP協議傳輸數據總量平均減少約9.26%,QPS平均增加約5.41%,說明BWMP協議對性能有一定的影響和提高。 表6 通信性能測試結果 4.3.3程序語言層面分析 眾所周知,編程語言的特性直接影響所實現系統的整體性能。本文原型系統和Linkerd都是基于Java實現,而Envoy則基于C++實現。C++是基于靜態類型編譯的編程語言,在本質上相比于Java更加高效。因此,從程序語言層面來看,Envoy的性能應該顯著優于BWM和Linkerd,但實驗結果是BWM和Envoy的性能相近,顯著優于采用同樣編程語言的Linkerd。因此考慮到編程語言本身對系統性能的影響,本文提出的服務網格性能優化方法起到了很好的優化效果。 服務網格是面向服務計算的新型基礎設施,為服務的靈活交互和全面的服務治理與服務監控提供支撐。服務網格的性能優化是提升系統并發處理能力的有效途徑。本文從計算模型和通信協議兩個方面提出了服務網格性能優化的關鍵技術,設計了基于Reactor的高性能并發模型和輕量化的通信協議與高效的編解碼算法。通過實驗與當前主流的服務網格系統Linkerd和Envoy進行了性能對比,分別從并發性能和通信性能方面進行分析比較,實驗結果表明本文方法在響應時間和網絡傳輸數據量方面均起到了性能優化作用。 目前本文的實驗只是基于簡單的模擬場景對提出方法的有效性進行了分析對比,后續工作將基于更多的真實應用更加深入和詳細地進行實驗和分析。此外,未來工作將從負載均衡和調度算法方面繼續深入研究服務網格的性能優化技術,并將相關成果應用于主流的開源服務網格系統,以進一步分析評估方法的有效性。3 系統設計

4 實 驗
4.1 實驗設計


4.2 評價指標

4.3 實驗結果分析




5 結 語