一、 Reactor and Proactor
IO讀寫時,多路復用機制都會依賴對一個事件多路分離器,負責把源事件的IO 事件分離出來,分別到相應的read/write事件分離器。涉及到事件分離器的兩種模式分別就是 Reactor和Proactor,Reactor是基於同步IO的,Proactor是基於異步IO的。
在Reactor模式中,事件分離者等待某個事件或者可應用或個操作的狀態發生(比如文件描述符可讀寫,或者是socket可讀寫),事件分離者就把這個事件傳給事先注冊的事件處理函數或者回調函數,由後者來做實際的讀寫操作。
在Proactor模式中,事件處理者(或者代由事件分離者發起)直接發起一個異步讀寫操作(相當於請求),而實際的工作是由操作系統來完成的。發起時,需要提供的參數包括用於存放讀到數據的緩存區,讀的數據大小,或者用於存放外發數據的緩存區,以及這個請求完後的回調函數等信息。事件分離者得知了這個請求,它默默等待這個請求的完成,然後轉發完成事件給相應的事件處理者或者回調。舉例來說,在Windows上事件處理者投遞了一個異步IO操作(稱有 overlapped的技術),事件分離者等IOCompletion事件完成. 這種異步模式的典型實現是基於操作系統底層異步API的,所以我們可稱之為“系統級別”的或者“真正意義上”的異步,因為具體的讀寫是由操作系統代勞的。
舉個例子,將有助於理解Reactor與Proactor二者的差異,以讀操作為例(類操作類似)。
在Reactor中實現讀:
- 注冊讀就緒事件和相應的事件處理器
- 事件分離器等待事件
- 事件到來,激活分離器,分離器調用事件對應的處理器。
- 事件處理器完成實際的讀操作,處理讀到的數據,注冊新的事件,然後返還控制權。
與如下Proactor(真異步)中的讀過程比較:
- 處理器發起異步讀操作(注意:操作系統必須支持異步IO)。在這種情況下,處理器無視IO就緒事件,它關注的是完成事件。
- 事件分離器等待操作完成事件
- 在分離器等待過程中,操作系統利用並行的內核線程執行實際的讀操作,並將結果數據存入用戶自定義緩沖區,最後通知事件分離器讀操作完成。
- 事件分離器呼喚處理器。
- 事件處理器處理用戶自定義緩沖區中的數據,然後啟動一個新的異步操作,並將控制權返回事件分離器。
可以看出,兩個模式的相同點,都是對某個IO事件的事件通知(即告訴某個模塊,這個IO操作可以進行或已經完成)。在結構
上,兩者也有相同點:demultiplexor負責提交IO操作(異步)、查詢設備是否可操作(同步),然後當條件滿足時,就回調handler;
不同點在於,異步情況下(Proactor),當回調handler時,表示IO操作已經完成;同步情況下(Reactor),回調handler時,表示
IO設備可以進行某個操作(can read or can write),handler這個時候開始提交操作。
二、BIO、NIO、AIO
NIO通常采用Reactor模式,AIO通常采用Proactor模式。AIO簡化了程序的編寫,stream的讀取和寫入都有OS來完成,不需要像NIO那樣子遍歷Selector。Windows基於IOCP實現AIO,Linux只有eppoll模擬實現了AIO。
Java7之前的JDK只支持NIO和BIO,從7開始支持AIO。
4種通信方式:TCP/IP+BIO, TCP/IP+NIO, UDP/IP+BIO, UDP/IP+NIO。
TCP/IP+BIO、
Socket和ServerSocket實現,ServerSocket實現Server端端口監聽,Socket用於建立網絡IO連接。
不適用於處理多個請求 1.生成Socket會消耗過多的本地資源。2. Socket連接的建立一般比較慢。
BIO情況下,能支持的連接數有限,一般都采取accept獲取Socket以後采用一個thread來處理,one connection one thread。無論連接是否有真正數據請求,都需要獨占一個thread。
可以通過設立Socket池來一定程度上解決問題,但是使用池需要注意的問題是:1. 競爭等待比較多。 2. 需要控制好超時時間。
TCP/IP+NIO
使用Channel(SocketChannel和ServerSocketChannel)和Selector。
Server端通常由一個thread來監聽connect事件,另外多個thread來監聽讀寫事件。這樣做的好處是這些連接只有在真是請求的時候才會創建thread來處理,one request one thread。這種方式在server端需要支持大量連接但這些連接同時發送請求的峰值不會很多的時候十分有效。
UDP/IP+BIO
DatagramSocket和DatagramPacket。DatagramSocket負責監聽端口以及讀寫數據,DatagramPacket作為數據流對象進行傳輸。
UDP/IP是無連接的,無法進行雙向通信,除非雙方都成為UDP Server。
UDP/IP+NIO
通過DatagramChannel和ByteBuffer實現。DatagramChannel負責端口監聽及讀寫。ByteBuffer負責數據流傳輸。
如果要將消息發送到多台機器,如果為每個目標機器都建立一個連接的話,會有很大的網絡流量壓力。這時候可以使用基於UDP/IP的Multicast協議傳輸,Java中可以通過MulticastSocket和DatagramPacket來實現。
Multicast一般多用於多台機器的狀態同步,比如JGroups。SRM, URGCP都是Multicast的實現方式。eBay就采用SRM來實現將數據從主數據庫同步到各個搜索節點機器。