1. 程式人生 > >一個執行緒阻塞引起的超時問題

一個執行緒阻塞引起的超時問題

背景

最近在測試一個線上的服務時,發現一個詭異的現象,服務在接收到使用者的請求後經過一系列邏輯處理後,將結果返回客戶端會耗費4到5秒的延遲。這是無法忍受的時間,那為什麼在應答給客戶端的時候需要這麼長時間呢? 測試發現,單次請求第三方的邏輯,不會出現這種問題,流程如下所示:

Created with Raphaël 2.1.0客戶端客戶端服務端服務端第三方第三方1、業務請求2、邏輯處理,轉發3、響應結果4、響應結果

而對於在兩次呼叫第三方介面的邏輯,才會出現這種問題,流程如下所示:

Created with Raphaël 2.1.0客戶端客戶端服務端服務端第三方第三方1、業務請求2、邏輯處理,轉發A
3、響應結果A4、邏輯處理,轉發B5、響應結果B6、響應結果

排查

服務端使用的netty框架開發,客戶端與服務端這段通訊走的是PB協議,服務端與第三方通訊直接呼叫的http介面,也是使用netty自己實現了一個httplient。延遲發生在客戶端與服務端之間,所以最先想到的是不是有異常大的包,但是抓包看到也就只有500個位元組,即使是公網也是毫秒級別搞定的事,問題不在這;接著開始懷疑負責服務端與客戶端處理的執行緒阻塞了,但是僅僅從debug程式碼去分析,沒辦法定位問題,在分析著eventloop執行的動作時也沒有找到非常耗時的任務,所以這個點在無法定位的情況下,只能暫時先擱置了,轉去懷疑傳送訊息的邏輯是不是有問題呢?看下發送的邏輯

hannel.writeAndFlush(encryptMsg).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            logger.info("serial no : {} back to clinet", clientSerialNo);
                            // 監控
MonitorRecorder.recordMessageBackend(transactionDto.getJnlNo()); } });

很簡單的一段程式碼,使用當前執行緒直接傳送,傳送完成之後列印一段日誌。這個時候考慮到一個問題,netty在處理一個channel的邏輯的時候,是一個執行緒全權處理的,進入writeAndFlush方法也可以發現,在傳送的時候會有一個判斷,這段程式碼很是經典,如下圖所示:
netty原始碼
如果呼叫writeAndFlush方法不是channel繫結的執行緒,那麼啟動一個任務,在執行這個任務的時候就是繫結到channel上的執行緒去執行writeAndFlush方法了,所以再回到問題上,對於一個channel來說,如果它繫結的執行緒阻塞了,那它後續的動作都會阻塞。那會不會是因為服務端在呼叫第三方的介面時阻塞掉了呢?再來看下這段傳送的程式碼:

private static void connect(final RequestRoot msg, final UserLogin userHead) throws Exception {
        Bootstrap b = getBoot();
        b.handler(new PfyhClientInitializer(new PfyhClientHandler(msg, userHead)));
        // Start the client.
        ChannelFuture f = b.connect(GlobalConstants.PFYH_TRADE_SERVER, GlobalConstants.PFYH_TRADE_PORT).sync();
        f.channel().closeFuture().get(GlobalConstants.MAX_WAIT_TIME, TimeUnit.SECONDS);
    }

傳送的時候會有最大等待時間,這個最大等待時間設定的10秒,如果未到10s,只有在連線主動關閉或者被動關閉的時候,該執行緒的阻塞才會結束,再聯絡到問題上,只有在兩次發到第三方介面時才會出現這個問題,那是不是第一次呼叫第三方介面的時候,連線沒有關呢?怒查程式碼。。 果然
原始碼

僅僅啟動了任務,對於第一次請求的資源沒有清理,增加了一句話就搞定了。

ctx.close();

但是還有一個問題,為什麼都是阻塞4到5秒呢?從上面的分析可以看到,一種是主動關閉連線還有一種是被動關閉連線,主動關閉設定的最大超時時間是10s,那麼被動設定的多少呢,抓個包可以瞅瞅
抓包
可以看到大概花費了5秒左右,第三方將第一次請求的連線關閉掉了