當前位置:
首頁 > 最新 > TARS為Spring Cloud 提供高性能的 RPC 能力

TARS為Spring Cloud 提供高性能的 RPC 能力

傳統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可以在同一個連接上不斷的寫入請求和接收響應,而客戶端通過請求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的主要通過上圖的過程來完成非同步調用,首先主調線程發起非同步調用,主調線程將請求內容加入網路線程池的發送隊列中,之後該線程繼續執行。網路線程池使用Reactor模型實現,通過nio提供的Selecter實現事件IO,所以所有網路線程均是事件驅動的非同步IO,當監聽到對應連接的寫事件後將請求發送,等待監聽到讀事件後讀取響應並交給回調線程處理響應。這樣所有的線程都避免了IO阻塞達到了更高的利用效率。

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

問題:

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

解決:

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

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

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

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

場景一.同步調用,性能提升一倍標題

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

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

場景二.非同步調用,避免阻塞,提升性能

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

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

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

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

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

因為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 !

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 java進階 的精彩文章:

資深架構師親述自己從一名碼農進階架構師的程序人生

TAG:java進階 |