2.2.3 偽異步I/O弊端分析
要對偽異步I/O的弊端進行深入分析,首先我們看兩個Java同步I/O的API說明,隨后我們結合代碼進行詳細分析。

代碼清單2-6 Java輸入流InputStream
請注意加粗斜體字部分的API說明,當對Socket的輸入流進行讀取操作的時候,它會一直阻塞下去,直到發生如下三種事件。
◎ 有數據可讀;
◎ 可用數據已經讀取完畢;
◎ 發生空指針或者I/O異常。
這意味著當對方發送請求或者應答消息比較緩慢、或者網絡傳輸較慢時,讀取輸入流一方的通信線程將被長時間阻塞,如果對方要60s才能夠將數據發送完成,讀取一方的I/O線程也將會被同步阻塞60s,在此期間,其他接入消息只能在消息隊列中排隊。
下面我們接著對輸出流進行分析,還是看JDK I/O類庫輸出流的API文檔,然后結合文檔說明進行故障分析。

代碼清單2-7 Java輸入流OutputStream
當調用OutputStream的write方法寫輸出流的時候,它將會被阻塞,直到所有要發送的字節全部寫入完畢,或者發生異常。學習過TCP/IP相關知識的人都知道,當消息的接收方處理緩慢的時候,將不能及時地從TCP緩沖區讀取數據,這將會導致發送方的TCP window size不斷減小,直到為0,雙方處于Keep-Alive狀態,消息發送方將不能再向TCP緩沖區寫入消息,這時如果采用的是同步阻塞I/O,write操作將會被無限期阻塞,直到TCP window size大于0或者發生I/O異常。
通過對輸入和輸出流的API文檔進行分析,我們了解到讀和寫操作都是同步阻塞的,阻塞的時間取決于對方I/O線程的處理速度和網絡I/O的傳輸速度。本質上來講,我們無法保證生產環境的網絡狀況和對端的應用程序能足夠快,如果我們的應用程序依賴對方的處理速度,它的可靠性就非常差。也許在實驗室進行的性能測試結果令人滿意,但是一旦上線運行,面對惡劣的網絡環境和良莠不齊的第三方系統,問題就會如火山一樣噴發。
偽異步I/O實際上僅僅只是對之前I/O線程模型的一個簡單優化,它無法從根本上解決同步I/O導致的通信線程阻塞問題。下面我們就簡單分析下如果通信對方返回應答時間過長,會引起的級聯故障。
(1)服務端處理緩慢,返回應答消息耗費60s,平時只需要10ms。
(2)采用偽異步I/O的線程正在讀取故障服務節點的響應,由于讀取輸入流是阻塞的,因此,它將會被同步阻塞60s。
(3)假如所有的可用線程都被故障服務器阻塞,那后續所有的I/O消息都將在隊列中排隊。
(4)由于線程池采用阻塞隊列實現,當隊列積滿之后,后續入隊列的操作將被阻塞。
(5)由于前端只有一個Accptor線程接收客戶端接入,它被阻塞在線程池的同步阻塞隊列之后,新的客戶端請求消息將被拒絕,客戶端會發生大量的連接超時。
(6)由于幾乎所有的連接都超時,調用者會認為系統已經崩潰,無法接收新的請求消息。如何破解這個難題?下節的NIO將給出答案。