中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么探討RPC框架中的服務線程隔離

發布時間:2021-12-27 14:04:24 來源:億速云 閱讀:125 作者:柒染 欄目:大數據

本篇文章給大家分享的是有關怎么探討RPC框架中的服務線程隔離,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

微服務如今應當是一個優秀的程序員必須學習的一種架構思想,而RPC框架作為微服務的核心,不說讀一遍源碼吧,起碼它的實現原理還是應該知道的。

然而目前的RPC服務框架,大多存在一個問題,就是當服務提供端Provider應用中,有的服務流量大,耗時長,導致線程池資源被這些服務占盡,從而影響同一應用中的其他服務正常提供。為此,下面主要介紹一下我對于這方面的思考。

 

前言

在進入正文之前,可以先看一下阿里中間件島風大佬的這篇博文(傳送門),這篇博文復現了Dubbo應用中,線程池耗盡的場景。這其實在線上是十分普遍,解決方法無非是根據業務調整參數,或者引入其他的限流、資源隔離框架,例如Hystrix、Sentinel等,使得資源間互不干擾。其實本身Dubbo也可以對不同的服務配置不同的業務線程池(通過配置protocol)從而實現服務的資源隔離,但是這種方式的弊端在于,一旦服務增多,線程數量會迅速膨脹。線程池過多不便于統一管理,同時過多的線程所帶來過多的上下文切換也會影響服務器性能。

在絕大多數場景下,對服務資源的隔離可以通過開源框架Sentinel來實現,其通過配置某個服務的并發數,來達到限流和線程資源隔離的目的。坦白的講,這已經能夠滿足絕大多數需求了,但是手動取配置這些參數還是比較有難度的,大多得靠大佬們的經驗了,而且也不夠靈活。

我在學習的時候,也突發奇想,有沒有可能不依賴外部的組件,而實現內部的服務資源隔離?再更進一步,有沒有可能根據應用內各個服務的流量數據,對每個服務資源進行動態的分配和綁定呢?

打個比方說,某個應用里存在A、B兩個服務,100個線程。白天的時候,A服務的流量大,B服務的流量很小,那么在這個時間段內,我們的應用分配給A的資源理應更多。但是也不能全給A拿走了,B也得喝口湯,不然又會出現線程耗盡的情況,所以此時我們可能根據流量數據的比對分給A服務80個線程,B服務20個線程;而到了晚上,A服務沒啥人用了,B服務流量來了,那我們就給B更多的資源,但也要保證A可用,比如說,A服務20線程,B服務80線程。

我承認我一開始只是想簡單寫個RPC框架,學習實現原理而已。但突然有了這樣一個想法,我就來了動力,想看看自己的想法行不行得通,下面我便介紹下我的思考,說的有不對的地方也歡迎大家指出和探討。

 

線程隔離的三個組件

借鑒了傳統的RPC框架的實現原理后,我們只需要修改或者增加三樣東西,就可以完成上述的功能,分別為:線程池、數據監控節點Metric和線程動態分配的Monitor。這三者之間的關系可以先看一下這張圖有個大概的印象。

怎么探討RPC框架中的服務線程隔離

 

線程池

首先需要修改的自然是線程池。以Dubbo為例,其默認的線程池為fixed線程池,io線程接收到請求后,委托Dubbo線程池完成后續的處理,通過調用ExecutorService.execute。

但是在這里,使用JDK中的線程池顯然是行不通了。線程池中的Thread也不再是單純的Thread,而需要更進一步的抽象。這里參考Netty中NioEventLoop的設計思想,將每條Thread抽象為一條Loop,其既是任務執行的本體Thread,也是ExecutorService的抽象,而所有Loop交由LoopGroup統一管理,由LoopGroup決定將任務提交至哪一個線程。這里我實現的比較簡單,每個線程有個專屬的id,通過拿到線程的id,將任務提交到對應的線程,原理可以參考下圖:

怎么探討RPC框架中的服務線程隔離

私以為核心在于維護服務與線程id的對應關系,以及在請求到來時,LoopGroup會根據請求中服務的類型,選擇對應id的線程,并交由該線程去處理請求。

 

數據監控

數據的監控相對來說是最好辦的。這里我參考了Sentinel的實現,使用時間窗口法統計各個服務的流量數據,包括pass、success、rt、reject、excetpion等。(關于Sentinel中的時間窗口,后面有時間再專門寫篇源碼分析)

而至于監控節點的形式,根據調用鏈路的具體實現不同,在Dubbo中可以是一個filter,而我因為將調用鏈路抽象為一個Pipeline,所以它作為Pipeline上的一個節點,參考下圖:

怎么探討RPC框架中的服務線程隔離

這里貼上MetricContext的關鍵源碼:

//處理請求時,pass+1,同時記錄開始時間并保存在線程上下文中
@Override
protected void handle(Object obj) {
   if(obj instanceof RpcRequest){
       RpcContext rpcContext=RpcContext.getContext();
       rpcContext.setStartTime(TimeUtil.currentTimeMillis());
       paladinMetric.addPass(1);
   }
}

//響應請求時,說明請求處理正常,則通過線程上下文拿到開始時間,
//計算出響應時間rt后將rt寫入統計數據,同時success+1
@Override
protected void response(Object obj) {
       RpcContext rpcContext=RpcContext.getContext();
       Long startTime=rpcContext.getStartTime();
       if(startTime!=null){
           Long rt=TimeUtil.currentTimeMillis()-startTime;
           paladinMetric.addRT(rt);
           paladinMetric.addSuccess(1);
           logger.warn(rpcContext.getRpcRequest().getClassName()
                   +":"
                   +rpcContext.getRpcRequest().getMethodName()
                   +" 's RT is "
                   +rt);
       }else{
           logger.error(rpcContext.getRpcRequest().getClassName()
                   +":"
                   +rpcContext.getRpcRequest().getMethodName()
                   +"has no start time!");
       }
}

//這里就是統一處理異常的方法,區分為普通異常和拒絕異常,
//如果是拒絕異常,說明線程已滿,拒絕添加任務,reject+1
@Override
protected void caughtException(Object obj) {
   paladinMetric.addException(1);
   if(obj instanceof RejectedExecutionException){
       paladinMetric.addReject(1);
   }
}
 

每個Context都會繼承AbstractContext,只需要實現handle、response和caughtException方法即可,由AbstractContext屏蔽了底層pipeline的順序調用。

 

線程分配

最后就是如何動態的將線程分配給服務。在這里,我們需要抽象一個評價模型,去評估各個服務應該占用多少資源(線程),可以參考下圖:

怎么探討RPC框架中的服務線程隔離


簡單來說,由于監控節點的存在,我們很容易就拿到每個服務的流量數據,然后抽象出每一個服務的評價模型,最后通過某種策略,得到線程分配的結果。

同時服務-線程的對應關系的讀寫,顯然是一個讀多寫少的場景。可以后臺開啟一個線程,每隔一段時間(比如20s),執行一次動態分配的策略。采用CopyOnWrite的思想,將對應關系的引用用volatile修飾,線程重新分配完成之后,直接替換掉其引用即可,這樣對性能的影響便沒有那么大了。

這里的問題在于,如何合理的制定分配的策略。由于我實在缺乏相應的經驗,所以寫的比較撈,希望有大佬可以指點一二。

 

效果如何

說了這么多,那我們便來看看效果如何。代碼我都放在了github上(由于時間比較短再加上本人菜,寫得比較粗糙,請大家見諒T T),代碼樣例都在paladin-demo模塊中,這里我就直接上結果了。

先定義一下參數,線程數總共20,每個服務最少能分配線程數為5,每條線程的阻塞隊列容量為4,服務端兩個服務,一個阻塞時間長,另一個無阻塞。

這里先定義一個阻塞時間長的服務HelloWorld。

怎么探討RPC框架中的服務線程隔離


然后我們通過http請求觸發任務,模擬大流量請求。

怎么探討RPC框架中的服務線程隔離


同時給出一個無阻塞的服務HelloPaladin,可以通過http訪問。

怎么探討RPC框架中的服務線程隔離


先后啟動服務服務提供端和消費端,開啟任務。控制臺直接炸裂。

怎么探討RPC框架中的服務線程隔離


服務瘋狂拋出拒絕異常。我們再輸入localhost:8080/helloPaladin?value=lalala,多點幾次,可以發現頁面很快就能返回結果,這也意味著這個服務并沒有被干擾。

最后我們來看一下,在任務啟動后,線程分配的情況如何:

22:15:06,653  INFO PaladinMonitor:81 - totalScore: 594807
22:15:06,653  INFO PaladinMonitor:91 - service: com.lcf.HelloPaladin:1.0.0_paladin, score: 1646
22:15:06,653  INFO PaladinMonitor:91 - service: com.lcf.HelloWorld:1.0.0_paladin, score: 593161
22:15:06,654  INFO PaladinMonitor:113 - Threads re-distribution result: {com.lcf.HelloPaladin:1.0.0_paladin=[1, 2, 3, 4, 5], com.lcf.HelloWorld:1.0.0_paladin=[6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]}
 

第一行輸出的是所有服務總共的分數,接下來兩行分別是兩個服務所得到的分數,最后一行是線程分配之后的結果。

我們穿插調用的HelloPaladin服務得到的分數遠遠低于跑任務的服務HelloWorld,但是由于設置了最小線程數,所以HelloPaladin服務分到了5條線程,而HelloWorld服務占據了其余的線程。(這里由于還開啟了一個單線程服務,所以沒有0號線程,至于什么是單線程服務可以看后文)

可以看到,服務間的線程資源確實隔離了,某一個服務的不可用不會影響到其他服務,同時資源也會向大流量的服務傾斜。

 

更花哨的玩法

在實現上面的功能之后,或許還有更加花哨的玩法。考慮這樣一個場景,如果某個服務存在頻繁加鎖的場景,那么多個線程并發加鎖執行,未必會有單個線程串行無鎖執行來的效率高,畢竟鎖和線程切換的開銷也不容忽視。

在實現了服務與線程的對應關系之后,這種串行無鎖執行的思路就很容易實現了,在初始化的時候,直接分配給這個服務固定的線程id號即可,這個線程也不會參與后續的動態分配流程。可以通過注解參數的方式來實現:


@RpcService(type = RpcConstans.SINGLE)
public class HelloSynWorldImpl implements HelloSynWorld
 

就是這么簡單,服務器啟動之后你就會發現,這個服務都會使用某條固定的線程去執行,自然也就用不著加鎖了(除非要跟其他服務同時操作共享資源,那就不適用于這種場景),不過這種串行場景我想了想,好像并不多,只有在那種純內存的操作中可能會比較有性能優勢(是不是很像Redis),所以也就圖一樂。

 

相比原來的線程模型有何優劣?

話又說回來了,雖然解決了服務資源隔離和分配的問題,那么相比原來的線程模型是否就沒有劣勢了呢?

因為加入了更多的組件,考慮到監控節點的性能損耗,增加了分配線程、選擇線程的邏輯,或許在性能上相比原來的線程模型會差一點,至于差多少,我可能也沒法定量給出解答,還需要進一步的測試。不過可以肯定的是,可以通過更多的優化,使得兩者的性能更加接近,例如:用JcTool的無鎖隊列替換JDK中的阻塞隊列;給出合適的評價模型,使得資源分配更合理以及分配過程性能更優等等。

當然最關鍵的還是你業務代碼寫的咋樣,畢竟框架優化的再好,業務代碼不大行,那點優化效果微乎其微。

以上就是怎么探討RPC框架中的服務線程隔離,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

rpc
AI

赞皇县| 綦江县| 织金县| 房产| 滨海县| 小金县| 温州市| 民和| 屯留县| 彰化市| 凤阳县| 吉木乃县| 鄱阳县| 久治县| 黎川县| 永胜县| 满城县| 涿州市| 康平县| 武宁县| 博湖县| 江津市| 雷波县| 卢氏县| 沂源县| 苍溪县| 四平市| 三台县| 平阴县| 宁安市| 咸阳市| 眉山市| 钟山县| 罗田县| 教育| 米脂县| 天全县| 定州市| 义马市| 本溪| 曲阳县|