前言
本文記錄了由於JSF非同步呼叫超時引起的介面可用率降低問題的排查過程,主要介紹了排查思路和JSF非同步呼叫的流程,希望可以幫助大家瞭解JSF的非同步呼叫原理以及提供一些問題排查思路。本文分析的JSF原始碼是基於JSF 1,7.5-HOTFIX-T6版本。
起因
問題背景
1.廣告投放系統是典型的I/O密集型(I/O Bound)服務,系統中某些介面單次操作可能依賴十幾個外部介面,導致介面耗時較長,嚴重影響使用者體驗,因此需要將這些外部呼叫切換為非同步模式,透過併發的模式降低整體耗時,提高介面的響應速度。
2.在同步呼叫的場景下,介面耗時長、效能差,介面響應時間長。這時為了縮短介面的響應時間,一般會使用執行緒池的方式並行獲取資料,但是如果使用執行緒池來做,不同業務需要不同的執行緒池,最後會導致難以維護,隨著CPU排程執行緒數的增加,會導致更嚴重的資源爭用,寶貴的CPU資源被損耗在上下文切換上,而且執行緒本身也會佔用系統資源,且不能無限增加。
3.透過閱讀JSF的文件發現JSF是支援非同步呼叫模式的,既然中介軟體已經支援這個功能,所以我們就採用了JSF提供的非同步呼叫模式,目前JSF支援三種非同步呼叫方式,分別是ResponseFuture方式、CompletableFuture方式和定義返回值為 CompletableFuture 的介面簽名方式。
(1)RpcContext中獲取ResponseFuture方式
該方式需要先將Consumer端的async屬性設定為true,代表開啟非同步呼叫,然後在呼叫Provider的地方使用RpcContext.getContext().getFuture()方法獲取一個ResponseFuture,拿到Future以後就可以使用get方法去阻塞等待返回,但是這種方式已經不推薦使用了,因為第二種CompletableFuture的模式更加強大。
程式碼示例:
asyncHelloService.sayHello("The ResponseFuture One");
ResponseFuture<Object> future1 = RpcContext.getContext().getFuture();
asyncHelloService.sayNoting("The ResponseFuture Two");
ResponseFuture<Object> 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屬性設定為true,代表開啟非同步呼叫,然後在呼叫Provider的地方使用RpcContext.getContext().getCompletableFuture()方法獲取到一個CompletableFuture進行後續操作。CompletableFuture對Future進行了擴充套件,可以透過設定回撥的方式處理計算結果,支援組合操作,也支援進一步的編排,一定程度解決了回撥地獄的問題。
程式碼示例:
asyncHelloService.sayHello("The CompletableFuture One");
CompletableFuture<String> cf1 = RpcContext.getContext().getCompletableFuture();
asyncHelloService.sayNoting("The CompletableFuture Two");
CompletableFuture<String> cf2 = RpcContext.getContext().getCompletableFuture();
CompletableFuture<String> cf3 = RpcContext.getContext().asyncCall(() -> {
asyncHelloService.sayHello("The CompletableFuture Three");
});
try {
cf1.get();
cf2.get();
cf3.get();
} catch (Throwable e) {
LOGGER.error("catch " + e.getClass()