【轉載】高性能IO設計 & Java NIO & 同步/異步 阻塞/非阻塞 Reactor/Proactor

開始準備看Java NIO的,這篇文章:http://xly1981.iteye.com/blog/1735862html

裏面提到了這篇文章 http://xmuzyq.iteye.com/blog/783218 同步、異步、阻塞、非阻塞、reactive、proactive等講的不錯。java

在高性能的I/O設計中,有兩個比較著名的模式Reactor和Proactor模式,
其中Reactor模式用於同步I/O, 而Proactor運用於異步I/O操做。

什麼是同步和異步react

同步和異步是針對應用程序和內核的交互而言的,
同步指的是用戶進程觸發IO操做並等待或者輪詢的去查看IO操做是否就緒,
而異步是指用戶進程觸發IO操做之後便開始作本身的事情,而當IO操做已經完成的時候會獲得IO完成的通知。

什麼是阻塞和非阻塞編程

阻塞和非阻塞是針對於進程在訪問數據的時候,根據IO操做的就緒狀態來採起的不一樣方式,
說白了是一種讀取或者寫入操做函數的實現方式,
阻塞方式下讀取或者寫入函數將一直等待,
而非阻塞方式下,讀取或者寫入函數會當即返回一個狀態值。

組合一下,就會發現通常來講I/O模型能夠分爲:同步阻塞,同步非阻塞,異步阻塞,異步非阻塞IOwindows

同步阻塞IO:
   在此種方式下,用戶進程在發起一個IO操做之後,必須等待IO操做的完成,只有當真正完成了IO操做之後,用戶進程才能運行。
JAVA傳統的IO模型屬於此種方式!
同步非阻塞IO: 在此種方式下,用戶進程發起一個IO操做之後邊可返回作其它事情,可是用戶進程須要時不時的詢問IO操做是否就緒
這就要求用戶進程不停的去詢問,從而引入沒必要要的CPU資源浪費。其中目前JAVA的NIO就屬於同步非阻塞IO
異步阻塞IO: 此種方式下是指應用發起一個IO操做之後,不等待內核IO操做的完成,等內核完成IO操做之後會通知應用程序
這其實就是同步和異步最關鍵的區別,同步必須等待或者主動的去詢問IO是否完成,而異步接受通知。
那麼爲何說是阻塞的呢
由於此時是經過select系統調用來完成的,而select函數自己的實現方式是阻塞的
而採用select函數有個好處就是它能夠同時監聽多個文件句柄,從而提升系統的併發性!
(注:同步異步區別不在與IO由操做系統仍是應用程序完成,而是在於主動詢問仍是等待通知;reactor和proactor區別,在於IO操做由哪邊完成,下面有提到)
異步非阻塞IO: 在此種模式下,用戶進程只須要發起一個IO操做而後當即返回,等IO操做真正的完成之後,應用程序會獲得IO操做完成的通知
此時用戶進程只須要對數據進行處理就行了,不須要進行實際的IO讀寫操做,由於真正的IO讀取或者寫入操做已經由內核完成了
目前Java中尚未支持此種IO模型。

 

搞清楚了以上概念之後,咱們再回過頭來看看,Reactor模式和Proactor模式(貌似都是非阻塞)設計模式

首先來看看Reactor模式,Reactor模式應用於同步I/O的場景緩存

咱們分別以讀操做和寫操做爲例來看看Reactor中的具體步驟:
讀取操做:
1. 應用程序註冊讀就需事件和相關聯的事件處理器
2. 事件分離器等待事件的發生
3. 當發生讀就需事件的時候,事件分離器調用第一步註冊的事件處理器
4. 事件處理器首先執行實際的讀取操做,而後根據讀取到的內容進行進一步的處理
寫入操做相似於讀取操做,只不過第一步註冊的是寫就緒事件

再看Proactor模式,Proactor主要應用於異步場景服務器

下面咱們來看看Proactor模式中讀取操做和寫入操做的過程:
讀取操做:
1. 應用程序初始化一個異步讀取操做,而後註冊相應的事件處理器,此時事件處理器不關注讀取就緒事件,而是關注讀取完成事件,這是區別於Reactor的關鍵2. 事件分離器等待讀取操做完成事件
3. 在事件分離器等待讀取操做完成的時候,操做系統調用內核線程完成讀取操做,並將讀取的內容放入用戶傳遞過來的緩存區中
這也是區別於Reactor的一點,Proactor中,應用程序須要傳遞緩存區
4. 事件分離器捕獲到讀取完成事件後,激活應用程序註冊的事件處理器,事件處理器直接從緩存區讀取數據而不須要進行實際的讀取操做。 Proactor中寫入操做和讀取操做,只不過感興趣的事件是寫入完成事件

從上面能夠看出,Reactor和Proactor模式的主要區別就是真正的讀取和寫入操做是有誰來完成的網絡

Reactor中須要應用程序本身讀取或者寫入數據多線程

而Proactor模式中,應用程序不須要進行實際的讀寫過程,它只須要從緩存區讀取或者寫入便可,操做系統會讀取緩存區或者寫入緩存區到真正的IO設備

 

綜上所述,同步和異步是相對於應用和內核的交互方式而言的同步須要主動去詢問,而異步的時候內核在IO事件發生的時候通知應用程序;

阻塞和非阻塞僅僅是系統在調用系統調用的時候函數的實現方式而已。

ReactorProactor這兩種常見的網絡編程模式,區別在於IO操做是由操做系統仍是應用程序來完成

 

還有這篇文章: http://blog.csdn.net/shallwake/article/details/5265287 增長理解,圖不錯。

首先,介紹幾種常見的I/O模型及其區別,以下:

  • blocking I/O

  • nonblocking I/O

  • I/O multiplexing (select and poll)

  • signal driven I/O (SIGIO)

  • asynchronous I/O (the POSIX aio_functions)

 

blocking I/O 

首先application調用 recvfrom()轉入kernel,注意kernel有2個過程wait for datacopy data from kernel to user。直到最後copy complete後,recvfrom()才返回。此過程一直是阻塞的。

 

nonblocking I/O: 

與blocking I/O對立的,非阻塞套接字,調用過程圖以下:

若是直接操做它,那就是個輪詢。。直到內核緩衝區有數據,而後會阻塞進行拷貝到用戶緩衝區,而後返回。

 

I/O multiplexing (select and poll) 

看下select的過程:

 

select先阻塞,有活動套接字才返回,而後用recvfrom去獲取數據。與blocking I/O相比,select會有兩次系統調用,可是select能處理多個套接字

 

signal driven I/O (SIGIO) 

只有Unix系統支持。

 

I/O multiplexing (select and poll)相比,它的優點是免去了select的阻塞與輪詢,當有活躍套接字時,由註冊的handler處理。 

 

asynchronous I/O (the POSIX aio_functions) 

不多有*nix系統支持,windows的IOCP則是此模型

徹底異步的I/O複用機制,由於縱觀上面其它四種模型,至少都會在由kernel copy data to appliction時阻塞。而該模型是當copy完成後才通知application,可見是純異步的。好像只有windows的完成端口是這個模型,效率也很出色。

 

下面是以上五種模型的比較

能夠看出,越日後,阻塞越少,理論上效率也是最優。

 

5種模型的比較比較清晰了,剩下的就是把select,epoll,iocp,kqueue按號入座那就OK了。

selectiocp分別對應第3種第5種模型,那麼epoll與kqueue呢? 其實也於select屬於同一種模型,只是更高級一些,能夠看做有了第4種模型的某些特性,如callback機制。

那麼,爲何epoll,kqueue比select高級? 

答案是,他們無輪詢。由於他們用callback取代了。想一想看,當套接字比較多的時候,每次select()都要經過遍歷FD_SETSIZE個Socket來完成調度,無論哪一個Socket是活躍的,都遍歷一遍。這會浪費不少CPU時間。若是能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操做,那就避免了輪詢,這正是epoll與kqueue作的。

 

windows or *nix (IOCP or kqueue/epoll)?

Windows的IOCP很是出色,目前不多有支持asynchronous I/O的系統,可是因爲其系統自己的侷限性,大型服務器仍是在UNIX下。

並且正如上面所述,kqueue/epoll 與 IOCP相比,就是多了一層從內核copy數據到應用層的阻塞,從而不能算做asynchronous I/O類。可是,這層小小的阻塞無足輕重,kqueue與epoll已經作得很優秀了。

 

提供一致的接口,IO Design Patterns

無論是哪一種模型,均可以抽象一層出來,提供一致的接口,廣爲人知的有ACE,Libevent這些,他們都是跨平臺的,並且他們自動選擇最優的I/O複用機制,用戶只需調用接口便可。

說到這裏又得說說2個設計模式,Reactor and Proactor。有一篇經典文章 http://www.artima.com/articles/io_design_patterns.html 值得閱讀,Libevent是Reactor模型ACE提供Proactor模型。實際都是對各類I/O複用機制的封裝

 

Java nio包是什麼I/O機制?

文中說NIO本質是select()模型。上一篇文章說NIO是同步非阻塞(輪詢),而select是異步阻塞,多是由於分類方法不一樣吧。

 

總結一些重點:

  1. 只有IOCP是asynchronous I/O,其餘機制或多或少都會有一點阻塞。
  2. select低效是由於每次它都須要輪詢。但低效也是相對的,視狀況而定,也可經過良好的設計改善
  3. epoll, kqueue是Reacor模式,IOCP是Proactor模式。
  4. java nio包是select模型。。

以上出處:link

 

下面這篇文章,是上面兩篇的補充 

http://www.smithfox.com/?e=191

 

首先這篇文章,說異步沒有阻塞非阻塞區別。異步就是異步! 只有同步時纔有阻塞和非阻塞之分。

 

說 阻塞和 非阻塞 時, 要區分場合範圍, 好比 Linux中說的 非阻塞I/O 和 Java的NIO1.0中的 非阻塞I/O 不是相同的概念. 

從最根原本說, 阻塞就是進程 "被" 休息, CPU處理其它進程去了. 非阻塞能夠理解成: 將大的整片時間的阻塞分紅N多的小的阻塞, 因此進程不斷地有機會 "被" CPU光顧, 理論上能夠作點其它事. 看上去 Linux非阻塞I/O 要比阻塞好, 但CPU會很大機率因socket沒數據而空轉. 雖然這個進程是爽了, 可是從整個機器的效率來講, 浪費更大了!  Java NIO1.0中的非阻塞I/O中的 Selector.select()函數仍是阻塞的, 因此不會有無謂的CPU浪費.

Java NIO1.0, 與其說是非阻塞I/O, 還不如說是, 多路複用I/O, 更好讓人理解!

 

異步

異步能夠說是I/O最理想的模型: CPU的原則是, 有必要的時候纔會參與, 既不浪費, 也不怠慢。

理想中的異步I/O: Application無需等待socket數據(也正是所以進程而被 "休息"), 也無需 copy socket data, 將由其它的同窗(理想狀態, 不是CPU) 負責將socket data copy到Appliation事先指定的內存後, 通知一聲Appliation(通常是回調函數).

傳統的阻塞socket有什麼問題?

最傳統的阻塞socket, 爲了避免導致處理一個client的請求時, 讓其它的client一直等, 通常會一個client鏈接, 就會起一個Thread.

實際狀況是, 有的業務, 只是client鏈接多, 但每一個client鏈接上的通信並非很是頻繁, 就算是很頻繁, 也會因網絡延遲, 
而使得大部分時間內,Thread們都在被"休息"(由於等待scoket上數據), 由於相對cpu的運算速度, 網絡延遲產生的間歇時間至關多.

這就形成了: 雖然Thread多, 並不能處理太多的socket請求,
要知道在JVM中每一個Thread都會單獨分配棧的(JVM默認好象是1M, 能夠經過 -Xss來調整),
並且需CPU要不斷地在不少線程之間switch, 保存/恢復 Thread Context 代價很是大!

多路複用

爲了解決阻塞I/O的問題, 就有了 I/O多路複用 模型, 多路複用就是用單獨的線程(是內核級的, 能夠認爲是高效的優化的) 來統一等待全部的socket上的數據, 一當某個socket上有數據後, 就啓用用戶線程(多是從線程池中取出, 而不是從新生成), copy socket data, 而且處理message.  

由於網絡延遲的緣由, 同時在處理socket data的用戶線程每每比實際的socket數量要少不少. 因此實際應用中, 大部分是用線程池, 池中thread數量可隨socket的高峯和低谷 而動態調整.

上面說的 多路複用I/O, 不少文章稱之爲   同步非阻塞(第一篇文章認爲是異步阻塞). 我的認爲, 不要老揪着那四個詞不放! 多累呀!

多路複用, 既能夠理解成 "非阻塞", 也能夠理解成 "阻塞"

多路複用I/O 中內核中統一的 wait socket data那部分能夠理解成 是 "非阻塞", 也能夠理解成"阻塞".

能夠理解成"非阻塞" 是由於它不是等到socket數據所有到達再處理, 而是有了一部分數據就會調用用戶線程來處理,

理解成"阻塞", 是由於它和用戶空間(Appliction)層的"非阻塞"socket的不一樣是: socket中沒有數據時, 內核仍是wait(阻塞)的, 
而用戶空間的非阻塞socket沒有數據也會返回, 會形成CPU的浪費(上面已經解釋過了).

select 和 poll

Linux下的 select和poll 就是 多路複用模式, poll 相對 select, 沒有了句柄數的限制, 但他們都是在內核層經過輪詢socket句柄的方式來實現的, 沒有利用更底層的 notify 機制.  但就算是這樣,相對阻塞socket 也已經進步了不少不少了! 畢竟用一個內核線程就解決了阻塞socket中N多線程都在無謂地wait的局面.

多路複用I/O 仍是讓用戶層來copy socket data. 這個過程是將內核中的socket buffer copy 到用戶空間的 buffer. 這有兩個問題: 一是多了一次內核空間switch到用戶空間的過程, 二是用戶空間層不便暴露很低層但很高效的copy 方式(好比DMA), 因此若是由內核層來作這個動做, 能夠更好地提升效率! DMA是指外部設備不經過CPU而直接與系統內存交換數據的接口技術。

epoll, Linux的AIO

因而, 在Linux2.6 epoll出現了, epoll是Linux下 AIO(異步IO)的實現方式, 實際上在epoll成爲最終方案以前, 也有其它的方案, 並且在其它的操做系統中都有着不一樣的AIO實現.

epoll 已經採用了更爲底層的 notify 機制, 而不是肓目地輪詢來實現, 這樣既減小了內核層的CPU消耗, 也使得上層的Application能更集中地關注應該關注的socket, 而沒必要每次都要將全部的 socket 經過 FD_ISSET來判斷一下.

更爲重要的是, epoll 由於採用 mmap的機制, 使得 內核socket buffer和 用戶空間的 buffer共享, 從面省去了 socket data copy, 這也意味着, 當epoll 回調上層的 callback函數來處理 socket 數據時, 數據已經從內核層 "自動" 到了用戶空間, 雖然和 用poll 同樣, 用戶層的代碼還必需要調用 read/write, 但這個函數內部實現所觸發的深度不一樣了,以下:

用 poll 時, poll  通知用戶空間的Appliation時, 數據還在內核空間, 
因此Appliation調用 read API 時, 內部會作 copy socket data from kenel space to user space. 而用 epoll 時, epoll 通知用戶空間的Appliation時, 數據已經在用戶空間,
因此 Appliation調用 read API 時, 只是讀取用戶空間的 buffer, 沒有 kernal space和 user space的switch了.

Java NIO和epoll

Java NIO也就是 NIO1.0 在Linux JDK6時已經改用 epoll 來做爲 default selectorProvider了.

因此, 我有一個最大的疑問: 是否能夠說, Java7中的 NIO2.0中的 AIO 改進已經沒法壓榨出 Linux2.6下epoll所帶來的好處了?! 畢竟NIO1.0 在JDK6時已經用過 epoll 了.

尚未來得及研究Java7中的NIO2.0, 但不管如何, NIO2.0從 framework層面所帶來的好處確定是很是深遠的.

Zero Copy

上面屢次提到 內核空間 和 用戶空間 的switch, 在socket read/write這麼小的粒度頻繁調用, 代價確定是很大的.

因此能夠在網上看到 Zero Copy的技術, 說到底 Zero Copy的思路就是:

分析你的業務, 看看是否能避免沒必要要的 跨空間copy,

好比能夠用 sendfile() 函數充分利用 內核能夠調用DMA 的優點,  直接在內核空間將文件的內容經過socket發送出去, 而沒必要通過用戶空間.

顯然, sendfile是有不少的前提條件的, 若是你想讓文件內容做一些變換再發出去, 就必需要通過 用戶空間的 Appliation logic, 也是沒法使用sendfile了.  

還有一種方式就是象 epoll 所作的, 用內存映射 mmap.

 

另,評論中有一句:應該說Java NIO中的Selector是多路複用,但NIO中也有非阻塞I/O的部分,由於咱們能夠用Channel而不用Selector。不知所云,待理解。

以上原文連接: http://www.smithfox.com/?e=191

 

proactor模式兩個要點: 
1.不註冊開始讀取狀態而註冊讀取完成狀態; 
2.應用程序把內存空間給到內核,而不是經過讀取或寫入。 

 

對第一篇的一個總結:

阻塞IO沒必要說了 
非阻塞IO ,IO請求時加上O_NONBLOCK一類的標誌位,馬上返回,IO沒有就緒會返回錯誤,須要請求進程主動輪詢不斷髮IO請求直到返回正確
IO複用同非阻塞IO本質同樣,不過利用了新的select系統調用,由內核來負責原本是請求進程該作的輪詢操做
看似比非阻塞IO還多了一個系統調用開銷,不過由於能夠支持多路IO,纔算提升了效率。
信號驅動IO,調用sigaltion系統調用,當內核中IO數據就緒時以SIGIO信號通知請求進程,請求進程再把數據從內核讀入到用戶空間,這一步是阻塞的。
異步IO,如定義所說,不會由於IO操做阻塞,IO操做所有完成才通知請求進程。
這樣以來,同步和阻塞,異步和非阻塞就不會被混淆了,它們不是同一個方面上的概念,不能比較區別 同步和異步是隻跟IO操做過程當中進程的狀態變化有關 阻塞和非阻塞就是進程的兩種狀態

以上是這篇:http://xly1981.iteye.com/blog/1735862

 

下面這篇文章講了 Java NIO

http://www.iteye.com/topic/472333

 

下面又是另外一種分類的解釋。。理解意思就行,都是文字遊戲。。

按照《Unix網絡編程》的劃分,IO模型能夠分爲:阻塞IO、非阻塞IO、IO複用、信號驅動IO和異步IO,

按照POSIX標準來劃分只分爲兩類:同步IO和異步IO。

如何區分呢?

首先一個IO操做其實分紅了兩個步驟:發起IO請求和實際的IO操做。

同步IO和異步IO的區別就在於第二個步驟是否阻塞,若是實際的IO讀寫阻塞請求進程,那麼就是同步IO,所以阻塞IO、非阻塞IO、IO複用、信號驅動IO都是同步IO;

若是不阻塞,而是操做系統幫你作完IO操做再將結果返回給你,那麼就是異步IO

阻塞IO和非阻塞IO的區別在於第一步,發起IO請求是否會被阻塞,若是阻塞直到完成那麼就是傳統的阻塞IO,若是不阻塞,那麼就是非阻塞IO。

 

Java nio 2.0的主要改進就是引入了異步IO(包括文件和網絡)。

參考非阻塞nio框架的設計,通常都是採用Reactor模式,Reacot負責事件的註冊、select、事件的派發;相應地,異步IO有個Proactor模式,Proactor負責 CompletionHandler的派發,查看一個典型的IO寫操做的流程來看二者的區別:

     Reactor:  
send(msg) -> 消息隊列是否爲空,
若是爲空 -> 向Reactor註冊OP_WRITE,而後返回 -> Reactor select -> 觸發Writable,通知用戶線程去處理
->先註銷Writable(不少人遇到的cpu 100%的問題就在於沒有註銷),處理Writeable,若是沒有徹底寫入,繼續註冊OP_WRITE。
注意到,寫入的工做仍是用戶線程在處理。
Proactor:
send(msg)
-> 消息隊列是否爲空,若是爲空,發起read異步調用,並註冊CompletionHandler,而後返回。
-> 操做系統負責將你的消息寫入,並返回結果(寫入的字節數)給Proactor -> Proactor派發CompletionHandler。
可見,寫入的工做是操做系統在處理,無需用戶線程參與。事實上在aio的API 中,AsynchronousChannelGroup就扮演了Proactor的角色。

 

下面這篇介紹nio: http://www.360doc.com/content/12/0914/09/820209_236025242.shtml

NIO出來以後,有了這麼幾個角色,ServerSocketChannel,SelectionKey,Selector. NIO中的對象跟reactor的對象對個象。

Acceptor:ServerSocketChannel;

Initiation Dispatcher:Selector;

HTTP Handler:針對SocketChannel進行實際處理的個性化對象;

Events:在SelectionKey中:

static int OP_ACCEPT 
          Operation-set bit for socket-accept operations.
static int OP_CONNECT 
          Operation-set bit for socket-connect operations.
static int OP_READ 
          Operation-set bit for read operations.
static int OP_WRITE 
          Operation-set bit for write operations.

上面一篇文章寫的很亂,看下面這篇吧:

http://blog.csdn.net/shirdrn/article/details/6263692

Java NIO模式的Socket通訊,是一種同步非阻塞IO設計模式,它爲Reactor模式實現提供了基礎。

NIO模式的基本原理描述以下:
服務端打開一個通道(ServerSocketChannel),並向通道中註冊一個選擇器(Selector),
這個選擇器是與一些感興趣的操做的標識(SelectionKey,即經過這個標識能夠定位到具體的操做,從而進行響應的處理)相關聯的,
而後基於選擇器(Selector)輪詢通道(ServerSocketChannel)上註冊的事件,並進行相應的處理。
客戶端在請求與服務端通訊時,也能夠向服務器端同樣註冊(比服務端少了一個SelectionKey.OP_ACCEPT操做集合),
並經過輪詢來處理指定的事件,而沒必要阻塞。

下面寫一下Java NIO的server和client。

另起一篇文章了,參考這篇文章:http://www.cnblogs.com/charlesblc/p/6074057.html

相關文章
相關標籤/搜索