TARS為SpringCloud提供高效能的RPC能力

yesye發表於2021-09-09

傳統HTTP存在的瓶頸

Spring Cloud 是一個優秀的開源微服務解決方案,通常採用 HTTP + json 的 REST 介面對外提供服務,簡潔易用部署方便,很多公司也基於 Spring Cloud 作為基礎架構去構建自身的微服務架構。但是隨著業務規模和使用者規模的增長,傳統基於的 HTTP 的服務會逐步暴露出一些問題。

首先是效能的問題,隨著使用者請求量的增長和業務邏輯複雜度的提升,我們會發現微服務的單機效能會成為系統瓶頸。

其次是穩定性問題,當一個服務節點A需要依賴於後端的幾個服務的時候,我們會發現當其中一個被依賴的服務發生卡頓,很可能會導致前端的服務節點A產生毛刺甚至無法繼續提供服務,而且當問題節點沒有能夠被及時遮蔽或者恢復的時候,還有可能會導致整個系統雪崩。


TARS如何為SpringCloud提供高效能解決方案

TARS 是騰訊從2008年到今天一直在使用的後臺邏輯層的統一應用框架,上述問題在 TARS 框架的發展過程中已經得到了比較好的解決。現在,TARS 通過外掛整合到 Spring Cloud 體系中,希望通過輸出 TARS 的 RPC 能力針對某些對效能和穩定性要求更高應用的場景提供一種新的解決方案,並且提供了基於 Spring Boot 的開發方式,符合Spring Cloud開發者的使用習慣,可以僅使用較小的開發成本在整個Spring Cloud體系中引入TARS的RPC能力。

將 TARS 結合到 Spring Cloud 中使用,通過 TARS 提供的長連線非同步呼叫和二進位制協議可以明顯的提升 RPC 呼叫效能。長連線通過連線複用減少整體的連線數量減少了資源消耗,同時通過二進位制協議提升了編解碼效率提升了整體的 RPC 效能。


一. 解決SpringCloud傳統HTTP網路連線使用率不高的問題

問題: 由於HTTP協議本身是無狀態的,所以發起一次請求的時候必須等待上一個請求響應才能再次使用這個連線,就算是採用流水線模式一個連線上的請求也會被之前發起的請求所阻塞,如果要提高併發能力則必須要建立大量的連線,而連線的建立、維持和銷燬都會消耗系統資源。

解決: 因為HTTP協議的特性,HTTP的回包是依賴於請求的先後順序的,必須要按照順序處理完一個請求再處理下一個請求,如果希望並行的處理請求則只能通過建立新的連結從而產生建連的時間開銷以及維護連線需要的CPU和記憶體資源。 

而TARS的協議設計是TARS的私有協議,每個請求會帶有一個請求id,通過同一個連結來傳送多個請求可以通過id來匹配返回從而避免了執行緒阻塞,從而降低了硬體資源消耗。

TARS為SpringCloud提供高效能的RPC能力

通過上圖可以看到,TARS可以在同一個連線上不斷的寫入請求和接收響應,而客戶端通過請求Id來關聯每一個請求和對應的響應,從而可以複用連線,避免了資源的浪費,通常情況下一個客戶端和一個服務端之間僅使用數個連線就可以滿足傳輸的要求。


二. 解決SpringCloud傳統HTTP通訊協議效能低下的問題

問題: HTTP + json 本身是一種可讀性很高的文字協議,因此實際傳輸的資料包會比二進位制協議要大不少,而且文字協議在資料的序列化反序列化效率上相比二進位制資料的效率要低很多,所以 HTTP 協議本身的效能就不高。

解決: TARS的資料傳輸採用的是TARS協議進行編解碼,TARS協議是一種二進位制協議,相較於常見的JSON等文字協議,二進位制協議主要有兩個方面的優勢: 

1. 編解碼效率 二進位制協議的編解碼是按二進位制位直接進行編解碼的,減少了對不確定的字串解析的過程,直接從對應的二進位制位讀取資料,效率相比解析文字協議有非常大的提升。

2. 網路包大小 因為所有的資料都是採用二進位制儲存,資料按位儲存減少了對空間的浪費,使得資料序列化後能減少對空間的佔用。 

TARS協議採用.tars檔案定義介面和資料介面,通過提供的工具可以將資料和介面定義翻譯成各種語言的程式碼實現。 

介面的共享只需要提供介面的定義檔案,使用者通過定義檔案直接生成客戶端介面程式碼即可。這樣減少了雙方的溝通成本,避免了需要寫大量的介面定義文件以及解析JSON所需的物件。


三. 解決SpringCloud傳統 HTTP服務基於同步執行緒模型的效能問題

問題: 傳統的 HTTP 服務多是基於同步的執行緒模型,由於 HTTP 協議本身無狀態,所以在協議層面就不支援非同步,所以當我們在客戶端發起一次 HTTP 呼叫時主調執行緒必須掛起等待被調響應請求,這個時候主調執行緒的資源則被浪費了,因為執行緒資源是有限的,大量執行緒被掛起等待白白浪費了主調方的運算資源。

解決: 相比於使用HTTP協議的常規方案,TARS首先提供的特性就是非同步長連線的RPC呼叫方式: 

發起一個非同步呼叫之後,當前執行緒並不會被阻塞而是繼續執行,當收到服務端響應之後在回撥執行緒池中通過回掉函式來執行結果的處理。這樣所有的處理執行緒都一直處於工作的狀態中,而不會掛起導致執行緒資源的浪費。整體上提升了服務的處理能力。

TARS的非同步能力主要是通過兩個部分的非同步來實現的,首先是網路首發包的非同步,TARS的網路層實現採用了Reactor模型,通過nio提供的事件IO實現基於事件的非同步網路IO。第二是執行緒模型的非同步,我們從執行緒模型上來看TARS如何是做到非同步呼叫的:

TARS為SpringCloud提供高效能的RPC能力

TARS的主要通過上圖的過程來完成非同步呼叫,首先主調執行緒發起非同步呼叫,主調執行緒將請求內容加入網路執行緒池的傳送佇列中,之後該執行緒繼續執行。網路執行緒池使用Reactor模型實現,通過nio提供的Selecter實現事件IO,所以所有網路執行緒均是事件驅動的非同步IO,當監聽到對應連線的寫事件後將請求傳送,等待監聽到讀事件後讀取響應並交給回撥執行緒處理響應。這樣所有的執行緒都避免了IO阻塞達到了更高的利用效率。


四. 解決SpringCloud服務端基於同步執行緒模型的穩定性問題

問題: 微服務的服務端基於同步的執行緒模型面臨的最大的隱患就是執行緒的IO等待,比如說一個基於同步的執行緒模型的微服務,依賴後面的3個介面,微服務本身的執行緒數是50個,那麼當後面依賴的3個介面中有一個延時飈高,只需要有50個對問題介面的呼叫,就足夠把整個微服務的程式掛起,因為當前已經沒有執行緒能夠對外提供服務了(當然可以設定超時,但是設定超時治標不治本)。同時,由於微服務執行緒對問題介面的IO等待,會導致微服務的佇列中堆積大量的等待時間過長(可能已經超時)的請求,當問題介面恢復後,服務端會消耗資源去處理大量的過期的請求(請求超時,客戶端不再等待)導致問題進一步惡化,嚴重的甚至會導致系統雪崩。

解決: TARS提供了純非同步化程式設計,和服務端過載保護的能力,在服務端保證收到大量的請求也能夠保證服務的正常處理效率,其次因為主調方採用長連線和非同步呼叫,避免了大量新建連線和阻塞帶來的資源浪費從而提升了服務的整體穩定性。

此外,如果服務端收到過量的請求往往會導致服務端的執行緒競爭,讓服務端的處理能力低於正常的處理水平,在TARS則通過佇列來進行過載保護。我們來看TARS的執行緒模型:

TARS為SpringCloud提供高效能的RPC能力

在網路執行緒收到請求後,TARS會將請求先加入請求佇列,工作執行緒從請求佇列中獲取請求進行處理,如果短時間內大量請求到達只會被快取到請求佇列中並不會影響工作執行緒池的處理能力。如果工作執行緒池從佇列中取到請求發現其已經超時則會直接丟棄請求避免處理無效的請求。


通過上面TARS的解決方案,看看實際的使用場景

場景一.同步呼叫,效能提升一倍標題

通常可以簡單可以簡單的改造服務,將本來的HTTP介面改為使用TARS,我們對比一下在同步呼叫場景下TARS呼叫和HTTP呼叫的效能差異,這裡規定了服務端執行緒數為100個執行緒,服務端的處理都為簡單的echo服務。我們對比一下在同一臺機器上不同RPC方式、不同的客戶端執行緒數以及不同資料包大小的TPS資料:

TARS為SpringCloud提供高效能的RPC能力

可以看出,因為採用了連線複用和二進位制的協議,整體的呼叫效率相比使用HTTP有了非常明顯的提升,而且是僅僅在簡單優化了一下呼叫方式的情況下,對業務處理邏輯並沒有影響。


場景二.非同步呼叫,避免阻塞,提升效能

假如我們在Spring Cloud中存在這樣一個呼叫關係,A服務需要呼叫B服務,而B服務需要依賴處理耗時遠大於是B服務的C服務。比如在通常的業務場景中,如果API介面需要呼叫一個訂單生成的服務,而訂單生成服務只需要生成訂單ID計算量相對較小,但是他還需要依賴一個訂單寫入服務,應為涉及到庫存修改、訂單寫入需要一系列的事務處理,整體耗時遠遠大於訂單ID的生成,所以訂單服務大量的執行緒資源浪費在了等待訂單寫入服務上。在這種情況下可以使用TARS改造訂單服務和寫入服務,從而使用非同步呼叫寫入服務來提升資源利用率,採用TARS提供的非同步RPC能力來進行跟深度的改造:

TARS為SpringCloud提供高效能的RPC能力

通常情況下如果使用同步呼叫,因為B需要等待C服務的響應,需要花上自身處理耗時的數倍來進行等待C服務返回結果,執行緒被阻塞浪費執行緒資源。這樣的情況我們可以保持A服務不變,提供REST介面,而B服務採用非同步呼叫來進行改造。如下圖所示:

TARS為SpringCloud提供高效能的RPC能力

我們通過簡單的程式碼來模擬上述過程,加入C服務的邏輯時收到請求後Sleep 10s後返回結果,C服務有10個處理執行緒,最開始B和C之間採用同步呼叫,要達到最大的併發效率B服務必須也提供10個執行緒才能夠達到最大的併發效率 TPS 為1。此時我們採用非同步呼叫改造B服務:

TARS為SpringCloud提供高效能的RPC能力

此時僅需核數 + 1個執行緒即可達到最大的處理效率。在通常的業務使用中,如果所有IO均用非同步實現,那麼只使用核數+1個執行緒便能達到較高的處理效率,從而避免了同步IO帶來的資源浪費。

對上述情況進行測試,我們規定C服務預設採用100個執行緒,服務的處理過程為Sleep 10s,用以模擬一個耗時比較高的資源服務。B服務為一個依賴資源服務C的普通服務,即收到C的結果即返回,在測試中B服務分別採用同步和非同步的方式呼叫C服務,通過調整執行緒數記錄B服務在不同執行緒數的情況下能提供的最大吞吐:

TARS為SpringCloud提供高效能的RPC能力

因為C服務能提供的最大TPS為10,可以看出使用TARS的非同步呼叫因為避免了阻塞,僅使用較少的執行緒數便可以達到對資源服務C的充分利用,從而避免了對資源的浪費。

在以上改造中,對外的HTTP介面並不需要改動,可以僅在內部需要提升RPC效能和用到非同步呼叫的地方進行改造即可,可以平滑的按服務逐步升級。而且因為均採用Spring boot實現,只需要修改介面,其餘所有業務程式碼還是使用Spring注入即可。


寫在最後

我們通過外掛實現了TARS對Eureka服務發現的支援,提供了Spring boot starter包和相關的註解,能夠通過符合Spring Cloud開發者習慣的開發方式快速開發服務。通過對服務發現和開發方式對Spring Cloud整合能夠讓開發者以較小的代價快速的在整個Spring Cloud的環境中快速引入TARS的RPC能力。

歡迎訪問TARS專案的Github地址:https://github.com/Tencent/Tars

試用最新的TARS on SpringCloud !


相關文章