【原文地址:http://www.cppblog.com/pansunyou/archive/2011/01/26/io_design_patterns.html】php
綜述html
這篇文章探討並比較兩種用於TCP服務器的高性能設計模式. 除了介紹現有的解決方案, 還提出了一種更具伸縮性,只須要維護一份代碼而且跨平臺的解決方案(含代碼示例), 以及其在不一樣平臺上的微調. 此文還比較了java,c#,c++對各自現有以及提到的解決方案的實現性能.java
系統I/O 可分爲阻塞型, 非阻塞同步型以及非阻塞異步型[1, 2]. 阻塞型I/O意味着控制權只到調用操做結束了纔會回到調用者手裏. 結果調用者被阻塞了, 這段時間了作不了任何其它事情. 更鬱悶的是,在等待IO結果的時間裏,調用者所在線程此時沒法騰出手來去響應其它的請求,這真是太浪費資源了。拿read()操做來講吧, 調用此函數的代碼會一直僵在此處直至它所讀的socket緩存中有數據到來.linux
相比之下,非阻塞同步是會當即返回控制權給調用者的。調用者不須要等等,它從調用的函數獲取兩種結果:要麼這次調用成功進行了;要麼系統返回錯誤標識告訴調用者當前資源不可用,你再等等或者再試度看吧。好比read()操做, 若是當前socket無數據可讀,則當即返回EWOULBLOCK/EAGAIN,告訴調用read()者"數據還沒準備好,你稍後再試".c++
在非阻塞異步調用中,稍有不一樣。調用函數在當即返回時,還告訴調用者,此次請求已經開始了。系統會使用另外的資源或者線程來完成此次調用操做,並在完成的時候知會調用者(好比經過回調函數)。拿Windows的ReadFile()或者POSIX的aio_read()來講,調用它以後,函數當即返回,操做系統在後臺同時開始讀操做。web
在以上三種IO形式中,非阻塞異步是性能最高、伸縮性最好的。算法
這篇文章探討不一樣的I/O利用機制並提供一種跨平臺的設計模式(解決方案). 但願此文能夠給於TCP高性能服務器開發者一些幫助,選擇最佳的設計方案。下面咱們會比較 Java, c#, C++各自對探討方案的實現以及性能. 咱們在文章的後面就再也不說起阻塞式的方案了,由於阻塞式I/O實在是缺乏可伸縮性,性能也達不到高性能服務器的要求。c#
兩種IO多路複用方案:Reactor and Proactor設計模式
通常狀況下,I/O 複用機制須要事件分享器(event demultiplexor [1, 3]). 事件分享器的做用,即將那些讀寫事件源分發給各讀寫事件的處理者,就像送快遞的在樓下喊: 誰的什麼東西送了, 快來拿吧。開發人員在開始的時候須要在分享器那裏註冊感興趣的事件,並提供相應的處理者(event handlers),或者是回調函數; 事件分享器在適當的時候會將請求的事件分發給這些handler或者回調函數.api
涉及到事件分享器的兩種模式稱爲:Reactor and Proactor [1]. Reactor模式是基於同步I/O的,而Proactor模式是和異步I/O相關的. 在Reactor模式中,事件分離者等待某個事件或者可應用或個操做的狀態發生(好比文件描述符可讀寫,或者是socket可讀寫),事件分離者就把這個 事件傳給事先註冊的事件處理函數或者回調函數,由後者來作實際的讀寫操做。
而在Proactor模式中,事件處理者(或者代由事件分離者發起)直接發起一個異步讀寫操做(至關於請求),而實際的工做是由操做系統來完成的。發起 時,須要提供的參數包括用於存放讀到數據的緩存區,讀的數據大小,或者用於存放外發數據的緩存區,以及這個請求完後的回調函數等信息。事件分離者得知了這 個請求,它默默等待這個請求的完成,而後轉發完成事件給相應的事件處理者或者回調。舉例來講,在Windows上事件處理者投遞了一個異步IO操做(稱有 overlapped的技術),事件分離者等IOCompletion事件完成[1]. 這種異步模式的典型實現是基於操做系統底層異步API的,因此咱們可稱之爲「系統級別」的或者「真正意義上」的異步,由於具體的讀寫是由操做系統代勞的。
舉另外個例子來更好地理解Reactor與Proactor兩種模式的區別。這裏咱們只關注read操做,由於write操做也是差很少的。下面是Reactor的作法:
下面再來看看真正意義的異步模式Proactor是如何作的:
現行作法
開源C++開發框架 ACE[1, 3](Douglas Schmidt, et al.開發) 提供了大量平臺獨立的底層併發支持類(線程、互斥量等). 同時在更高一層它也提供了獨立的幾組C++類,用於實現Reactor及Proactor模式。 儘管它們都是平臺獨立的單元,但他們都提供了不一樣的接口.
ACE Proactor在MS-Windows上不管是性能還在健壯性都更勝一籌,這主要是因爲Windows提供了一系列高效的底層異步API. [4, 5].
(這段可能過期了點吧) 不幸的是,並非全部操做系統都爲底層異步提供健壯的支持。舉例來講, 許多Unix系統就有麻煩.所以, ACE Reactor多是Unix系統上更合適的解決方案. 正由於系統底層的支持力度不一,爲了在各系統上有更好的性能,開發者不得不維護獨立的好幾份代碼: 爲Windows準備的ACE Proactor以及爲Unix系列提供的ACE Reactor.
就像咱們提到過的,真正的異步模式須要操做系統級別的支持。因爲事件處理者及操做系統交互的差別,爲Reactor和Proactor設計一種通用統一的外部接口是很是困難的。這也是設計通行開發框架的難點所在。
更好的解決方案
在文章這一段時,咱們將嘗試提供一種融合了Proactor和Reactor兩種模式的解決方案. 爲了演示這個方案,咱們將Reactor稍作調整,模擬成異步的Proactor模型(主要是在事件分離器裏完成本該事件處理者作的實際讀寫工做,咱們稱這種方法爲"模擬異步")。 下面的示例能夠看看read操做是如何完成的:
咱們看到,經過爲分離者(也就上面的調試者)添加一些功能,可讓Reactor模式轉換爲Proactor模式。全部這些被執行的操做,實際上是和 Reactor模型應用時徹底一致的。咱們只是把工做打散分配給不一樣的角色去完成而已。這樣並不會有額外的開銷,也不會有性能上的的損失,咱們能夠再仔細 看看下面的兩個過程,他們實際上完成了同樣的事情:
標準的經典的 Reactor模式:
模擬的Proactor模式:
在沒有底層異步I/O API支持的操做系統,這種方法能夠幫咱們隱藏掉socket接口的差別(不管是性能仍是其它), 提供一個徹底可用的統一"異步接口"。這樣咱們就能夠開發真正平臺獨立的通用接口了。
TProactor
咱們提出的TProactor方案已經由TerabitP/L [6]公司實現了. 它有兩種實現: C++的和Java的.C++版本使用了ACE平臺獨立的底層元件,最終在全部操做系統上提供了統一的異步接口。
TProactor中最重要的組件要數Engine和WaitStrategy了. Engine用於維護異步操做的生命週期;而WaitStrategy用於管理併發策略. WaitStrategy和Engine通常是成對出現的, 二者間提供了良好的匹配接口.
Engines和等待策略被設計成高度可組合的(完整的實現列表請參照附錄1)。TProactor是高度可配置的方案,經過使用異步內核API和同步Unix API(select(), poll(), /dev/poll (Solaris 5.8+), port_get (Solaris 5.10),RealTime (RT) signals (Linux 2.4+), epoll (Linux 2.6), k-queue (FreeBSD) ),它內部實現了三種引擎(POSIX AIO, SUN AIO and Emulated AIO)並隱藏了六類等待策略。TProactor實現了和標準的 ACE Proactor同樣的接口。這樣一來,爲不一樣平臺提供通用統一的只有一份代碼的跨平臺解決方案成爲可能。
Engines和WaitStrategies能夠像樂高積木同樣自由地組合,開發者能夠在運行時經過配置參數來選擇合適的內部機制(引擎和等待策略)。 能夠根據需求設定配置,好比鏈接數,系統伸縮性,以及運行的操做系統等。若是系統支持相應的異步底層API,開發人員能夠選擇真正的異步策略,不然用戶也 能夠選擇使用模擬出來的異步模式。全部這一切策略上的實現細節都不太須要關注,咱們看到的是一個可用的異步模型。
舉例來講,對於運行在Sun Solaris上的HTTP服務器,若是須要支持大量的鏈接數,/dev/poll或者port_get()之類的引擎是比較合適的選擇;若是須要高吞吐 量,那使用基本select()的引擎會更好。因爲不一樣選擇策略內在算法的問題,像這樣的彈性選擇是標準ACE Reactor/Proactor模式所沒法提供的(見附錄2)。
在性能方面,咱們的測試顯示,模擬異步模式並未形成任何開銷,沒有變慢,反卻是性能有所提高。根據咱們的測試結果,TProactor相較標籤的ACE Reactor在Unix/Linux系統上有大約10-35%性能提高,而在Windows上差很少(測試了吞吐量及響應時間)。
性能比較 (JAVA / C++ / C#).
除了C++,咱們也在Java中實現了TProactor. JDK1.4中, Java僅提供了同步方法, 像C中的select() [7, 8]. Java TProactor基於Java的非阻塞功能(java.nio包),相似於C++的TProactor使用了select()引擎.
圖一、2顯示了以 bits/sec爲單位的傳輸速度以及相應的鏈接數。這些圖比較瞭如下三種方式實現的echo服務器:標準ACE Reactor實現(基於RedHat Linux9.0)、TProactor C++/Java實現(Microsoft Windows平臺及RedHat v9.0), 以及C#實現。測試的時候,三種服務器使用相同的客戶端瘋狂地鏈接,不間斷地發送固定大小的數據包。
這幾組測試是在相同的硬件上作的,在不一樣硬件上作的相對結果對比也是相似。
用戶代碼示例
下面是TProactor Java實現的echo服務器代碼框架。總的來講,開發者只須要實現兩個接口:一是OpRead,提供存放讀結果的緩存;二是OpWrite,提供存儲待 寫數據的緩存區。同時,開發者須要經過回調onReadComplated()和onWriteCompleted()實現協議相關的業務代碼。這些回調 會在合適的時候被調用.
class EchoServerProtocol implements AsynchHandler
{
AsynchChannel achannel = null;
EchoServerProtocol( Demultiplexor m, SelectableChannel channel )
throws Exception
{
this.achannel = new AsynchChannel( m, this, channel );
}
public void start() throws Exception
{
// called after construction
System.out.println( Thread.currentThread().getName() +
": EchoServer protocol started" );
achannel.read( buffer);
}
public void onReadCompleted( OpRead opRead ) throws Exception
{
if ( opRead.getError() != null )
{
// handle error, do clean-up if needed
System.out.println( "EchoServer::readCompleted: " +
opRead.getError().toString());
achannel.close();
return;
}
if ( opRead.getBytesCompleted () <= 0)
{
System.out.println("EchoServer::readCompleted: Peer closed "
+ opRead.getBytesCompleted();
achannel.close();
return;
}
ByteBuffer buffer = opRead.getBuffer();
achannel.write(buffer);
}
public void onWriteCompleted(OpWrite opWrite)
throws Exception
{
// logically similar to onReadCompleted
...
}
}
結束語
TProactor爲多個平臺提供了一個通用、彈性、可配置的高性能通信組件,全部那些在附錄2中提到的問題都被很好地隱藏在內部實現中了。
從上面的圖中咱們能夠看出C++仍舊是編寫高性能服務器最佳選擇,雖然Java已緊隨其後。然而由於Java自己實現上的問題,其在Windows上表現不佳(這已經應該成爲歷史了吧)。
須要注意的是,以上針對Java的測試,都是以裸數據的形式測試的,未涉及到數據的處理(影響性能)。
縱觀AIO在Linux上的快速發展[9], 咱們能夠預計Linux內核API將會提供大量更增強健的異步API, 如此一來之後基於此而實現的新的Engine/等待策略將能輕鬆地解決能用性方面的問題,而且這也能讓標準ACE Proactor接口受益。
附錄 I
TProactor中實現的Engines 和 等待策略
引擎類型 | 等待策略 | 操做系統 |
---|---|---|
POSIX_AIO (true async)aio_read() /aio_write() |
aio_suspend() |
POSIX complained UNIX (not robust) POSIX (not robust) SGI IRIX, LINUX (not robust) |
SUN_AIO (true async)aio_read() /aio_write() |
aio_wait() |
SUN (not robust) |
Emulated Async Non-blocking read() /write() |
select() poll() /dev/poll Linux RT signals Kqueue |
generic POSIX Mostly all POSIX implementations SUN Linux FreeBSD |
附錄 II
全部同步等待策略可劃分爲兩組:
select()
, poll()
, /dev/poll) - readiness at any time.讓咱們看看這兩組的一些廣泛的邏輯問題:
資源
[1] Douglas C. Schmidt, Stephen D. Huston "C++ Network Programming." 2002, Addison-Wesley ISBN 0-201-60464-7
[2] W. Richard Stevens "UNIX Network Programming" vol. 1 and 2, 1999, Prentice Hill, ISBN 0-13- 490012-X
[3] Douglas C. Schmidt, Michael Stal, Hans Rohnert, Frank Buschmann "Pattern-Oriented Software Architecture: Patterns for Concurrent and Networked Objects, Volume 2" Wiley & Sons, NY 2000
[4] INFO: Socket Overlapped I/O Versus Blocking/Non-blocking Mode. Q181611. Microsoft Knowledge Base Articles.
[5] Microsoft MSDN. I/O Completion Ports.
http://msdn.microsoft.com/library/default.asp?url=/library/en- us/fileio/fs/i_o_completion_ports.asp
[6] TProactor (ACE compatible Proactor).
www.terabit.com.au
[7] JavaDoc java.nio.channels
http://java.sun.com/j2se/1.4.2/docs/api/java/nio/channels/package-summary.html
[8] JavaDoc Java.nio.channels.spi Class SelectorProvider
http://java.sun.com/j2se/1.4.2/docs/api/java/nio/channels/spi/SelectorProvider.html
[9] Linux AIO development
http://lse.sourceforge.net/io/aio.html, and
http://archive.linuxsymposium.org/ols2003/Proceedings/All-Reprints/Reprint-Pulavarty-OLS2003.pdf
更多
Ian Barile "I/O Multiplexing & Scalable Socket Servers", 2004 February, DDJ
Further reading on event handling
- http://www.cs.wustl.edu/~schmidt/ACE-papers.html
The Adaptive Communication Environment
http://www.cs.wustl.edu/~schmidt/ACE.html
Terabit Solutions
http://terabit.com.au/solutions.php
關於做者
Alex Libman has been programming for 15 years. During the past 5 years his main area of interest is pattern-oriented multiplatform networked programming using C++ and Java. He is big fan and contributor of ACE.
Vlad Gilbourd works as a computer consultant, but wishes to spend more time listening jazz :) As a hobby,he started and runs www.corporatenews.com.au website.