說說高并發IO原理及模型,結果沒結果了
被虐前奏
面試官:說一下高并發IO底層原理?
面試者:呃。。。嗯。。。這個那個。。。我們都是用XX框架
結果:卒
理解一下高并發原理,展現真正的實力
一、IO讀寫基礎原理
IO讀寫分為read和write兩塊,在不同的操作系統中,IO讀寫的底層調用可能有些區別,但基本功能是一樣的。read調用,并不是直接從物理磁盤讀取,同樣,write調用也不是直接把數據寫進物理磁盤,在他們中間還有一層,就是緩沖區,而緩沖區又包括:內核(kernel)緩沖區和用戶進程緩沖區。具體可以看下圖

在Java中,完整的Socket請求和響應如下:
- 客戶端調用:系統通過網卡讀取客戶端的請求,將數據讀取到內核緩沖區;
- 程序請求數據:Java通過read命令,將數據從內核緩沖區送入Java的用戶緩沖區;
- Java服務端數據處理:在Java用戶空間中處理客戶請求數據;
- Java服務端返回數據:數據處理完,通過write命令,將構建好的響應數據從用戶緩沖區寫入內核緩沖區;
- 發送數據到客戶端:內核通過網絡IO,將內核緩沖區中的數據通過網卡,適時將數據發送給目標客戶端。
二、四種常見的IO模型
1、同步阻塞IO(Blocking IO)
我們先了解一下概念:
同步與異步:同步指的是用戶空間是主動發起IO請求的一方,內核空間是被動接受的一方;異步則是將以上過程反過來;
阻塞與非阻塞:阻塞指的用戶空間發起請求后,需要等待內核空間徹底完成后,才將數據返回到用戶空間;非阻塞指的是用戶空間不需要等待內核空間操作完成,可以立即返回用戶用戶空間的過程 。
傳統的IO模型都是同步阻塞IO,在Java中,默認創建的socket都是同步阻塞的。Java應用從發起IO調用開始,直到系統調用返回,Java進程都是阻塞的,直到有數據返回成功后,應用程序才能開始處理用戶緩沖區的數據,具體流程如下:

由上圖可以看出,每一步必須等待上一步的完成才能進行。
同步阻塞IO優點:開發簡單,容易入門;在阻塞等待期間,用戶線程掛起,在掛起期間不會占用CPU資源。
同步阻塞IO缺點:一個線程維護一個IO,不適合大并發,在并發量大的時候需要創建大量的線程來維護網絡連接,內存、線程開銷灰常大。
2、同步非阻塞IO(Non-blocking IO)
參考以上概念,當處于非阻塞IO時,用戶空間與內核空間可以更快的交互,只要拿到內核返回的狀態值,就可以繼續干自己的事情,而不用做無謂的等待。
在Java中,將socket的屬性設置為NONBLOCK即為非阻塞模式。在這個模式中,會有以下兩種情況:
在內核緩沖區沒有數據時,系統調用會馬上返回失敗狀態給調用方;
在內核緩沖區有數據時,此時是會阻塞的,直到數據從內核緩沖區復制到用戶緩沖區,復制完成后,系統調用返回成功,繼而用戶的應用進程開始處理用戶空間的數據,具體流程如下:

由上圖可以看出,在內核還沒有準備好數據的時候,用戶線程發起IO請求,會立即返回;為了最終讀取到數據,用戶線程需要不斷發起IO調用;歷經多次探險后,終于有數據到達內核時,此時,用戶線程會進入阻塞狀態,當數據成功復制到用戶緩沖區,用戶線程成功讀取到數據后,才會解除阻塞狀態,重新運行起來。
同步非阻塞IO優點:每次發起IO調用,在內核等待數據的過程中可以立即返回,用戶線程不會阻塞,實時性較好。
同步非阻塞IO缺點:多個線程不斷輪詢內核是否有數據,占用大量CPU時間,效率不高。一般Web服務器不會采用此模式。
3、IO多路復用(IO Multiplexing)
如何解決同步非阻塞IO輪詢造成效率低下的問題,這時IO多路復用出現了。
經典的Reactor反應器設計模式,也可稱為異步阻塞IO,是眾多高性能高并發服務器的基礎,比如Nginx、Netty、Redis等。Java中NIO(New IO)里的Selector選擇器也是基于此模式。在Linux系統中,對應的系統調用為select/epoll系統調用,當內核緩沖區可讀或者可寫時,內核將就緒的狀態主動通知相應的用戶程序,應用程序收到就緒狀態,進行相應的IO調用,具體流程如下:

由上圖可以看出,該模式下,read操作流程如下:
- 注冊選擇器,將read操作的目標socket提前注冊到select/epol選擇器中(類比Java中NIO的selector);
- 輪詢,通過選擇器的查詢方法,查詢注冊過的所有socket連接的就緒狀態,內核個就緒的socket列表(當內核監控到注冊過的socket有數據準備好了,就會將該socket加入到就緒列表)。注意,當用戶調用select查詢方法時,此時整個線程會處于阻塞狀態;
- 用戶線程獲取到就緒列表后,根據其中的socket連接,發起read請求,用戶線程阻塞,內核開始復制數據,將數據從內核緩沖區復制到用戶緩沖區;
- 復制完成后,內核返回結果,用戶線程才會解除阻塞,待用戶線程讀取成功數據后,線程繼續。
IO多路復用優點:系統不必創建維護大量線程,只使用一個線程,一個選擇器即可同時處理成千上萬個連接,大大減少了系統開銷。
IO多路復用缺點:本質上,select/epoll系統調用是阻塞式的,屬于同步IO,需要在讀寫事件就緒后,由系統調用進行阻塞的讀寫。
4、異步IO(Asynchronous IO)
異步IO,指的是用戶空間與內核空間的調用方式返過來。內核空間是主動調用者,用戶空間變成被動接收者。用戶空間的線程向內核空間注冊各種IO事件的回調函數,由內核主動觸發調用。
AIO的基本流程是:用戶線程通過系統調用,向內核注冊某個IO操作。在整個內核的數據處理中, 包括數據從網卡到內核緩沖區,內核緩沖區到用戶緩沖區,用戶程序都不需要阻塞。具體流程如下:

由上圖可以看出,該模式下,在內核等待數據和復制數據的兩個階段,用戶線程都不會阻塞。read操作流程如下:
- 用戶發起read請求,內核立刻返回成功狀態,用戶不會阻塞,可以馬上去做其它事情;
- 內核開始準備數據,待數據準備好了,內核會主動將數據從內核緩沖區復制到用戶緩沖區;
- 此時,內核會給用戶線程發送一個信號,或者回調用戶線程注冊的回調接口,通知用戶read操作完成了;
- 用戶線程收到通知后,讀取用戶緩沖區的數據,完成后續業務。
異步IO優點:真正實現了異步非阻塞,吞吐量在這幾種模式中是最高的。
異步IO缺點:應用程序只需要進行事件的注冊與接收,其余工作都交給了操作系統內核,所以需要內核提供支持。在Linux系統中,異步IO在其2.6才引入,目前也還不是灰常完善,其底層實現仍使用epoll,與IO多路復用相同,因此在性能上沒有明顯占優。