那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

記一次JSF異步調(diào)用引起的接口可用率降低

京東云 ? 來源:jf_75140285 ? 作者:jf_75140285 ? 2024-08-05 13:40 ? 次閱讀

前言

本文記錄了由于JSF異步調(diào)用超時引起的接口可用率降低問題的排查過程,主要介紹了排查思路和JSF異步調(diào)用的流程,希望可以幫助大家了解JSF的異步調(diào)用原理以及提供一些問題排查思路。本文分析的JSF源碼是基于JSF 1,7.5-HOTFIX-T6版本。

起因

問題背景

1.廣告投放系統(tǒng)是典型的I/O密集型(I/O Bound)服務(wù),系統(tǒng)中某些接口單次操作可能依賴十幾個外部接口,導(dǎo)致接口耗時較長,嚴(yán)重影響用戶體驗(yàn),因此需要將這些外部調(diào)用切換為異步模式,通過并發(fā)的模式降低整體耗時,提高接口的響應(yīng)速度。

2.在同步調(diào)用的場景下,接口耗時長、性能差,接口響應(yīng)時間長。這時為了縮短接口的響應(yīng)時間,一般會使用線程池的方式并行獲取數(shù)據(jù),但是如果使用線程池來做,不同業(yè)務(wù)需要不同的線程池,最后會導(dǎo)致難以維護(hù),隨著CPU調(diào)度線程數(shù)的增加,會導(dǎo)致更嚴(yán)重的資源爭用,寶貴的CPU資源被損耗在上下文切換上,而且線程本身也會占用系統(tǒng)資源,且不能無限增加。

3.通過閱讀JSF的文檔發(fā)現(xiàn)JSF是支持異步調(diào)用模式的,既然中間件已經(jīng)支持這個功能,所以我們就采用了JSF提供的異步調(diào)用模式,目前JSF支持三種異步調(diào)用方式,分別是ResponseFuture方式、CompletableFuture方式和定義返回值為 CompletableFuture 的接口簽名方式。

(1)RpcContext中獲取ResponseFuture方式

該方式需要先將Consumer端的async屬性設(shè)置為true,代表開啟異步調(diào)用,然后在調(diào)用Provider的地方使用RpcContext.getContext().getFuture()方法獲取一個ResponseFuture,拿到Future以后就可以使用get方法去阻塞等待返回,但是這種方式已經(jīng)不推薦使用了,因?yàn)榈诙NCompletableFuture的模式更加強(qiáng)大。

代碼示例:

asyncHelloService.sayHello("The ResponseFuture One");
ResponseFuture future1 = RpcContext.getContext().getFuture();
asyncHelloService.sayNoting("The ResponseFuture Two");
ResponseFuture future2 = RpcContext.getContext().getFuture();
try {
     future1.get();
     future2.get();
} catch (Throwable e) {
    LOGGER.error("catch " + e.getClass().getCanonicalName() + " " + e.getMessage(), e);
}

(2)RpcContext中獲取CompletableFuture方式(1.7.5及以上版本支持)

該方式需要先將Consumer端的async屬性設(shè)置為true,代表開啟異步調(diào)用,然后在調(diào)用Provider的地方使用RpcContext.getContext().getCompletableFuture()方法獲取到一個CompletableFuture進(jìn)行后續(xù)操作。CompletableFuture對Future進(jìn)行了擴(kuò)展,可以通過設(shè)置回調(diào)的方式處理計(jì)算結(jié)果,支持組合操作,也支持進(jìn)一步的編排,一定程度解決了回調(diào)地獄的問題。

代碼示例:

asyncHelloService.sayHello("The CompletableFuture One");
CompletableFuture cf1 = RpcContext.getContext().getCompletableFuture();
asyncHelloService.sayNoting("The CompletableFuture Two");
CompletableFuture cf2 = RpcContext.getContext().getCompletableFuture();

CompletableFuture cf3 = RpcContext.getContext().asyncCall(() -> {
    asyncHelloService.sayHello("The CompletableFuture Three");
});
try {
    cf1.get();
    cf2.get();
    cf3.get();
} catch (Throwable e) {
    LOGGER.error("catch " + e.getClass().getCanonicalName() + " " + e.getMessage(), e);
}

(3)使用 CompletableFuture 簽名的接口(1.7.5及以上版本支持)

這種模式需要改造代碼,需要服務(wù)的提供者事先定義方法的返回值簽名為CompletableFuture,這種調(diào)用端無需配置即可使用異步。

代碼示例:

CompletableFuture cf4 = asyncHelloService.sayHelloAsync("The CompletableFuture Fore");
cf4.whenComplete((res, err) -> {
    if (err != null) {
        LOGGER.error("interface async cf4 now complete error " + err.getClass().getCanonicalName() + " " + err.getMessage(), err);
    } else {
        LOGGER.info("interface async cf4 now complete : {}", res);
    }
});
CompletableFuture cf5 = asyncHelloService.sayNotingAsync("The CompletableFuture Five");

try {
    LOGGER.info("interface async cf1 now is : {}", cf4.get());
    LOGGER.info("interface async cf2 now is : {}", cf5.get());
} catch (Throwable e) {
    LOGGER.error("catch " + e.getClass().getCanonicalName() + " " + e.getMessage(), e);
}

通過對已上三種異步調(diào)用模式的分析,第三種需要提供者修改方法簽名支持異步,難以實(shí)現(xiàn);本著改動最小化,API使用最優(yōu)化,我們最終選擇了第二種方式,即在調(diào)用端設(shè)置async屬性為true,同時在發(fā)起調(diào)用后從RpcContext中獲取一個CompletableFuture對象進(jìn)行后續(xù)的操作。

問題現(xiàn)象

經(jīng)過異步模式改造,部分依賴很多外部服務(wù)的接口耗時有明顯的下降,表面看系統(tǒng)一片祥和,但是偶爾的接口可用率降低卻是一個非常危險(xiǎn)的信號,下面是使用異步調(diào)用的某個接口的可用率監(jiān)控

wKgaomawZdCALPlwAANtYGwH48c701.png

通過監(jiān)控我們可以發(fā)現(xiàn),這個接口偶爾會出現(xiàn)可用率降低,一般接口可用率降低可能是因?yàn)槌瑫r或者觸發(fā)了某些隱藏問題導(dǎo)致,但是這個接口的邏輯非常簡單,就是根據(jù)id查詢數(shù)據(jù)庫,業(yè)務(wù)邏輯非常簡單,理論上不應(yīng)該出現(xiàn)這么多可用率降低的情況。我們通過日志排查發(fā)現(xiàn)在異步調(diào)用使用CompletableFuture的get方法阻塞等待的時候發(fā)生了TimeOutException異常,目前接口配置的超時時間為5s,本來接口超時是一個我們經(jīng)常遇見的問題,但是我們?nèi)ヌ峁┱叨瞬樵內(nèi)罩景l(fā)現(xiàn),本次請求只耗費(fèi)了幾毫秒,明明提供者端幾毫秒或者幾十毫秒就返回了,為什么消費(fèi)端還超時了,帶著這個疑問我們繼續(xù)分析,會不會是JSF異步的原因?qū)е碌摹?/p>

排查定位原因

通過閱讀JSF的源碼,我們了解到JSF異步調(diào)用的基本流程為客戶端向服務(wù)端發(fā)送請求前,會先判斷本次請求是否需要走異步調(diào)用,如果需要的話,會生成一個JSFCompletableFuture對象 這個類是繼承自CompletableFuture的,同時使用一個futureMap對象緩存了請求的唯一msgId和一個MsgFuture對象,MsgFuture對象里面持有了本次調(diào)用使用的channel、message、timeout、compatibleFuture等屬性,方便服務(wù)端回調(diào)后,可以通過msgId找到對應(yīng)的MsgFuture對象做后續(xù)處理。

首先在doSendAsyn方法里生成MsgId和MsgFuture對象的映射,然后序列化數(shù)據(jù),最后通過netty的長連接向channel里面寫入要發(fā)送的數(shù)據(jù)。

(1)生成JSFCompletableFuture

wKgaomawZdGALPZfAAEQAxJEssA138.png

(2)維護(hù)msgId和MsgFuture的關(guān)系

wKgaomawZdKAE_pPAAIgSmSsgwU590.png

(3) 維護(hù)msgId和MsgFuture的關(guān)系

wKgZomawZdOAfbk5AASPPCP-OnM808.png

(4)發(fā)起調(diào)用

wKgaomawZdSAMZX1AAGNn9VUa2g758.png

服務(wù)端收到請求后,會觸發(fā)服務(wù)端的ServerChannelHandler類的channelRead方法被回調(diào),這個方法里面會驗(yàn)證序列化協(xié)議,然后生成一個JSFTask的任務(wù),將這個任務(wù)提交到JSF的業(yè)務(wù)線程池去執(zhí)行,等業(yè)務(wù)線程池里的任務(wù)執(zhí)行完成以后,會調(diào)用write方法將返回值通過channel寫回客戶端。

(1)服務(wù)端收到響應(yīng)處理

wKgZomawZdWAEgaPAAaw9DURHJ8755.png

(2)服務(wù)端回寫響應(yīng)

wKgaomawZdaAX1H2AAJi4417ZZU275.png

客戶端收到響應(yīng)后,會觸發(fā)客戶端的ClientChannelHandler類的channelRead方法,這個方法里面會通過服務(wù)端返回的msgId找到客戶端緩存的MsgFuture對象,然后會判斷對象內(nèi)的compatibleFuture屬性是不是非空,如果非空,會往Callback線程池內(nèi)提交一個任務(wù),這個任務(wù)的主要功能是執(zhí)行CompletableFuture的completeExceptionally和complete方法,用于觸發(fā)CompletableFuture的下一階段執(zhí)行。

(1)客戶端收到響應(yīng)

wKgZomawZdaAWv9EAAEFn1nOTp8648.png

(2)找到本地的MsgFuture

wKgaomawZdeABC2OAAFVIyDNQ5U890.png

(3)將MsgFuture添加到線程池

wKgZomawZdiAD-ghAAFH6T719QA734.png

(4) 觸發(fā)CompletableFuture的complete或者completeExceptionally方法

wKgZomawZdmAPCnMAAE1C2rUUvw577.png

通過對已上源碼的分析,我們雖然知道了JSF異步調(diào)用的全部流程,但是還是無法解釋為什么偶爾會出現(xiàn)不應(yīng)該超時的超時(此處指服務(wù)端明明沒有超時,客戶端還顯示超時了),通過對各個流程的排除,我們最終定位到可能和JSF異步回調(diào)后將任務(wù)添加到Callback線程池去執(zhí)行CompletableFuture的complete方法有關(guān),因?yàn)檫@個方法會繼續(xù)執(zhí)行CompletableFuture后續(xù)的階段,我們業(yè)務(wù)代碼在拿到RpcContext里面返回的CompletableFuture對象以后,一般會使用CompletableFuture的一元依賴方法ThenApply去執(zhí)行一些后續(xù)處理,CompletableFuture的complete方法就是用來觸發(fā)這些后續(xù)階段去執(zhí)行的。

異步調(diào)用業(yè)務(wù)代碼:

wKgaomawZdqAbKubAAkDcSEAs34861.png

下面介紹一下CompletableFuture的基礎(chǔ)知識,每個CompletableFuture都可以被看作一個被觀察者,其內(nèi)部有一個Completion類型的鏈表成員變量stack,用來存儲注冊到其中的所有觀察者。當(dāng)被觀察者執(zhí)行完成后會彈棧stack屬性,依次通知注冊到其中的觀察者,所以在這個階段會去調(diào)用我們程序中的ThenApply方法,下圖是CompletableFuture內(nèi)部的關(guān)鍵屬性。

wKgZomawZdqAPOjsAACXDPQ3moY747.png

如果上面的異步調(diào)用流程感覺不清晰,可以看下面的一張調(diào)用關(guān)系圖

wKgaomawZduALd7QAAOYpj-4WIs544.jpg

?

通過查看Callack線程池的默認(rèn)配置,發(fā)現(xiàn)他的核心線程數(shù)為20,隊(duì)列長度256,最大線程數(shù)200。看到這我們猜測可能是核心線程數(shù)不夠用,導(dǎo)致一些回調(diào)任務(wù)積壓在隊(duì)列中沒來得及執(zhí)行導(dǎo)致了超時。由于無法通過其他方式獲取當(dāng)時CallBack線程池的運(yùn)行狀態(tài),因此我們通過修改業(yè)務(wù)代碼,在發(fā)生超時異常的時候獲取Callback線程池當(dāng)前的狀態(tài)來驗(yàn)證我們的猜測。

(1)獲取線程池狀態(tài)代碼

wKgZomawZdyAAUTfAAYAZiranbw873.png

修改完代碼上線后,系統(tǒng)運(yùn)行一段時間出現(xiàn)了接口可用率降低的現(xiàn)象,接著我們查詢?nèi)罩荆瑥娜罩纠锟梢钥闯觯诎l(fā)生超時異常的時候,JSF的Callback線程池核心線程數(shù)已滿,同時隊(duì)列中積壓了71個任務(wù),通過這個日志就可以確定是因?yàn)镴SF 回調(diào)線程池核心線程數(shù)滿導(dǎo)致任務(wù)排隊(duì)出現(xiàn)的超時

wKgZomawZd2AV283AAWQvRUdFyQ251.png

問題分析

1、通過上面的日志我們知道是因?yàn)楫惒骄€程池滿導(dǎo)致的,理論上正常請求就算有些排隊(duì)?wèi)?yīng)該也會很快就能處理掉,但是我們排查業(yè)務(wù)代碼后發(fā)現(xiàn),我們有些業(yè)務(wù)在ThenApply里面做了一些耗時的操作、還有在ThenApply里面又調(diào)用了另外一個異步方法。

2、第一種情況會導(dǎo)致線程池的線程會被一直占用,其他任務(wù)都會在排隊(duì),這種其實(shí)還是能接受的,但是第二種情況可能會出現(xiàn)線程池循環(huán)引用導(dǎo)致死鎖,原因是父任務(wù)會將異步回調(diào)放在線程池執(zhí)行,父任務(wù)的子任務(wù)也會將異步回調(diào)放在線程池執(zhí)行,Callback線程池核心線程大小為20,當(dāng)同一時刻有20個請求到達(dá),則Callback core thread被打滿,子任務(wù)請求線程時進(jìn)入阻塞隊(duì)列排隊(duì),但是父任務(wù)的完成又依賴于子任務(wù),這時由于子任務(wù)得不到線程,父任務(wù)無法完成,主線程執(zhí)行g(shù)et進(jìn)入阻塞狀態(tài),并且永遠(yuǎn)無法恢復(fù)。

解決方案

短期方案:因?yàn)榫€程池核心線程滿導(dǎo)致排隊(duì),所以將JSF 的回調(diào)線程池核心線程數(shù)從20調(diào)整為200,

長期方案:優(yōu)化代碼將ThenApply里面耗時的操作不放在回調(diào)線程池執(zhí)行,同時優(yōu)化代碼邏輯,將在ThenApply方法內(nèi)部再次開啟異步調(diào)用的流程去除。

調(diào)整完前后的對比:

wKgaomawZd-AZPMjAAQijnwHjzM110.png

通過查看監(jiān)控可以發(fā)現(xiàn),優(yōu)化后接口可用率一直保持在100%。

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8691

    瀏覽量

    151911
  • 異步
    +關(guān)注

    關(guān)注

    0

    文章

    62

    瀏覽量

    18095
  • JSF
    JSF
    +關(guān)注

    關(guān)注

    0

    文章

    12

    瀏覽量

    7764
收藏 人收藏

    評論

    相關(guān)推薦

    關(guān)于C語言同步調(diào)用,回調(diào),異步調(diào)用

    最近在看C語言異步調(diào)用方面的知識同步調(diào)用也稱之為堵塞式調(diào)用,就是調(diào)用方必須等被調(diào)用方執(zhí)行完畢并返回值后才接著執(zhí)行,這個比較好理解。回調(diào) 很多
    發(fā)表于 09-09 10:37

    異步調(diào)用的妙用

    本帖最后由 yk74110 于 2023-2-21 15:07 編輯 LabVIEW的異步調(diào)用使用非常方便,輕松實(shí)現(xiàn)多窗口獨(dú)立并行的程序架構(gòu),非常值得學(xué)習(xí)借鑒。以上作品屬于本人原創(chuàng)分析范例,供學(xué)習(xí)交流使用。
    發(fā)表于 05-02 03:04

    LabVIEW異步調(diào)用,子VI關(guān)閉問題

    消息處理器完成初始化,以及后期的些不依靠前面板控件的處理過程。事件響應(yīng),前面板控件響應(yīng),以及用戶事件,這里的用戶事件定義的是串口中斷收到的數(shù)據(jù),配合事件響應(yīng)接收數(shù)據(jù)。事件響應(yīng)中用到異步調(diào)用,想法是:打開
    發(fā)表于 08-16 20:41

    開啟異步調(diào)用后 又接了關(guān)閉引用

    開啟異步調(diào)用后 又接了關(guān)閉引用這個程序還能運(yùn)行 且依舊能執(zhí)行 想問下關(guān)閉引用又有什么作用 不關(guān)閉又怎么樣
    發(fā)表于 05-18 11:30

    我想問如果我異步調(diào)用可重入 參數(shù)是X80會怎么樣

    本帖最后由 woshisu 于 2018-6-6 20:39 編輯 異步調(diào)用的x80是不等待結(jié)果 x40是異步調(diào)用可重入VI?? 我想問如果我異步調(diào)用可重入 參數(shù)是X80會怎么樣我再補(bǔ)充下我
    發(fā)表于 06-06 19:38

    labview異步調(diào)用導(dǎo)致鼠標(biāo)拖動中斷

    本帖最后由 羊駝啊 于 2019-7-9 10:13 編輯 不知道是否有大佬深入了解過異步調(diào)用。我是很久之前使用異步調(diào)用,做了個后臺并行計(jì)時工具。這段程序是大概間隔1s就會執(zhí)
    發(fā)表于 07-08 23:09

    一次網(wǎng)站設(shè)計(jì)稿的方法

    一次網(wǎng)站設(shè)計(jì)稿
    發(fā)表于 06-16 09:43

    什么是同步調(diào)制和異步調(diào)

    在PWM控制電路中,載波頻率 fc 和調(diào)制信號頻率 fr 之比成為載波比,根據(jù)載波信號和信號波信號是否同步分為同步調(diào)制和異步調(diào)制。1 什么是異步調(diào)制?載波信號和調(diào)制信號不保持同步的方式稱為異步
    發(fā)表于 09-03 08:43

    labview異步調(diào)用vi

    labview 異步調(diào)用斗個實(shí)例并行
    發(fā)表于 12-07 14:37

    異步調(diào)用個VI的程序,生成安裝包時,該如何設(shè)置路徑啊

    異步調(diào)用個VI的程序,生成安裝包時,該如何設(shè)置路徑啊,exe調(diào)用的還是原來的vi,但是生成程序安裝包,不知道該如何設(shè)置這個路徑了,或者是程序打包需要設(shè)置什么嗎
    發(fā)表于 09-28 19:56

    異步調(diào)用子vi問題

    我試了異步調(diào)用子vi,現(xiàn)在的問題是子vi是個循環(huán),但是我在主程序獲取子vi的結(jié)果時,只有子vi結(jié)束了才能獲取且只能獲取到循環(huán)最后一次的結(jié)果,怎么樣才能在主程序中獲取子vi循環(huán)實(shí)時的結(jié)果呢?而且等待結(jié)果好像并沒有起到
    發(fā)表于 11-11 10:34

    LabVIEW中如何調(diào)試異步調(diào)用的VI?

    大佬們,我想在異步調(diào)用的VI中插入探針,查看數(shù)據(jù),應(yīng)該怎么操作,LabVIEW如何調(diào)試異步調(diào)用的VI呀?網(wǎng)上都沒查到啥結(jié)果。
    發(fā)表于 07-28 11:19

    Labview的異步調(diào)用示例工程文件免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是Labview的異步調(diào)用示例vi工程文件免費(fèi)下載
    發(fā)表于 10-14 08:00 ?22次下載
    Labview的<b class='flag-5'>異步調(diào)用</b>示例工程文件免費(fèi)下載

    異步調(diào)制和同步調(diào)制各有何優(yōu)缺點(diǎn)

    異步調(diào)制和同步調(diào)制是數(shù)字通信中兩種常見的調(diào)制方式。它們各自具有優(yōu)缺點(diǎn),適用于不同的應(yīng)用場景。 異步調(diào)制 定義 異步調(diào)制(Asynchro
    的頭像 發(fā)表于 08-14 11:12 ?3053次閱讀

    pwm同步調(diào)制和異步調(diào)制的區(qū)別

    PWM(Pulse Width Modulation,脈沖寬度調(diào)制)是種常見的調(diào)制方式,廣泛應(yīng)用于通信、控制等領(lǐng)域。PWM調(diào)制分為同步調(diào)制和異步調(diào)制兩種方式,它們在性能、應(yīng)用場景等方面存在
    的頭像 發(fā)表于 08-14 11:15 ?2650次閱讀
    百家乐官网定位胆技巧| 大发888宫网| 百家乐官网最保险的方法| 澳门百家乐娱乐注册| 篮球比分直播| 都坊百家乐官网的玩法技巧和规则| 全讯网3344111| 真人百家乐官网轮盘| 凯斯百家乐的玩法技巧和规则| 百家乐官网揽子打法| 百家乐五湖四海赌场娱乐网规则| 百家乐官网扑克玩法| 百家乐云顶| 百家乐官网总厂在哪里| 至尊百家乐网| 百家乐官网庄闲局部失衡| 永利百家乐的玩法技巧和规则| 百家乐官网必胜法hk| 网络百家乐破| 赌场百家乐官网台| 大发888城亚洲游戏| 王子百家乐官网的玩法技巧和规则 | 百家乐官网群sun811.com| 太阳城菲律宾官方网| 百家乐官网小九梭哈| 韦德国际| 百家乐视频看不到| 筠连县| 百家乐技巧方法| 百家乐官网投注限额| 水果机8键遥控器| 百家乐官网平注法口诀| 威尼斯人娱乐城怎样赢| 百家乐官网旺门打| 灵山县| 新澳门百家乐的玩法技巧和规则| 百家乐官网实时赌博| 娱网棋牌官方网站| 百家乐正反投注| 线上百家乐官网开户| 大发888|