《两种高性能IO设计模式的比较.docx》由会员分享,可在线阅读,更多相关《两种高性能IO设计模式的比较.docx(9页珍藏版)》请在第壹文秘上搜索。
1、两种高性能I/O设计模式(ReaCtor/ProdCtor)的比较这篇文章研讨并比较两种用于TCP服务器的高性能设计模式,除了介绍现有的解决方案,还提出了一种更具伸缩性,只需要维护一份代码并且跨平台的解决方案(含代码示例),以及其在不同平台上的微调.此文还比较了java,c#,c+对各自现有以及提到的解决方案的实现性能.系统I/O可分为堵塞型,非堵塞同步型以及非堵塞异步型口,2.堵塞型I/O意味着掌握权只到调用操作结束了才会回到调用者手里.结果调用者被堵塞了,这段时间了做不了任何其它事情.更郁闷的是,在等待IO结果的时间里,调用者所在线程此时无法腾出手来去响应其它的恳求,这真是太铺张资源了。拿
2、ead()操作来说吧,调用此函数的代码会始终僵在此处直至它所读的socket缓存中有数据到来.相比之下,非堵塞同步是会马上返回掌握权给调用者的。调用者不需要等等,它从调用的函数猎取两种结果:要么此次调用胜利进行了;要么系统返回错误标识告知调用者当前资源不行用,你再等等或者再试度看吧。比如read。操作,假如当前SOCket很多据可读,则马上返回Ewoulblock/eagain,告知调用read。者”数据还没预备好,你稍后再试在非堵塞异步调用中,稍有不同。调用函数在马上返回时,还告知调用者,这次恳求已经开头了。系统会使用此外的资源或者线程来完成这次调用操作,并在完成的时候知会调用者(比如通过回
3、调函数)。拿WindoWS的ReadFile()或者PoSlX的aio_read()来说,调用它之后,函数马上返回,操作系统在后台同时开头读操作。在以上三种IO形式中,非堵塞异步是性能最高、伸缩性最好的。这篇文章研讨不同的I/O采用机制并供应一种跨平台的设计模式(解决方案).盼望此文可以给于TCP高性能服务器开发者一些关心,选择最佳的设计方案。下面我们会比较Java,c#,C+各自对研讨方案的实现以及性能,我们在文章的后面就不再提及堵塞式的方案了,由于堵塞式I/O实在是缺少可伸缩性,性能也达不到高性能服务器的要求。两种IO多路复用方案:ReaCtOrandProactor一般状况下,I/O复用
4、机制需要大事共享器(eventdemultiplexor1,3).大事共享器的作用,即将那些读写大事源分发给各读写大事的处理者,就像送快递的在楼下喊:谁的什么东西送了,快来拿吧。开发人员在开头的时候需要在共享器那里注册感爱好的大事,并供应相应的处理者(eventhandlers),或者是回调函数;大事共享器在适当的时候会将恳求的大事分发给这些handler或者回调函数.涉及到大事共享器的两种模式称为:ReactorandProactor1.ReaCtOr模式是基于同步I/O的,而PrOaCtor模式是和异步I/O相关的.在ReaCtOr模式中,大事分别者等待某个大事或者可应用或个操作的状态发生
5、(比如文件描述符可读写,或者是SoCket可读写),大事分别者就把这个大事传给事先注册的大事处理函数或者回调函数,由后者来做实际的读写操作。而在ProaCt。模式中,大事处理者(或者代由大事分别者发起)直接发起一个异步读写操作(相当于恳求),而实际的工作是由操作系统来完成的。发起时,需要供应的参数包括用于存放读到数据的缓存区,读的数据大小,或者用于存放外发数据的缓存区,以及这个恳求完后的回调函数等信息。大事分别者得知了这个恳求,它悄悄等待这个恳求的完成,然后转发完成大事给相应的大事处理者或者回调。举例来说,在WindOWS上大事处理者投递了一个异步IO操作(称有overlapped的技术),大
6、事分别者等IOCompIetion大事完成1.这种异步模式的典型实现是基于操作系统底层异步APl的,所以我们可称之为系统级别的或者真正意义上的异步,由于详细的读写是由操作系统代劳的。举此外个例子来更好地理解ReaCtOr与ProaCtor两种模式的区分。这里我们只关注read操作,由于write操作也是差不多的。下面是Reactor的做法: 某个大事处理者宣称它对某个socket上的读大事很感爱好; 大事分别者等着这个大事的发生; 当大事发生了,大事分别器被唤醒,这负责通知从前那个大事处理者; 大事处理者收到消息,于是去那个SoCket上读数据了.假如需要,它再次宣称对这个SoCket上的读大
7、事感爱好,始终重复上面的步骤;下面再来看看真正意义的异步模式Proactor是如何做的: 大事处理者直接投递发一个写操作(当然,操作系统必需支持这个异步操作).这个时候,大事处理者根本不关怀读大事,它只管发这么个恳求,它魂牵梦萦的是这个写操作的完成大事这个处理者很拽,发个命令就不管详细的事情了,只等着别人(系统)帮他搞定的时候给他回个话。 大事分别者等着这个读大事的完成(比较下与Reactor的不同); 当大事分别者悄悄等待完成事情到来的同时,操作系统己经在一边开头干活了,它从目标读取数据,放入用户供应的缓存区中,最终通知大事分别者,这个事情我搞完了; 大事共享者通知之前的大事处理者:你叮嘱的
8、事情搞定了; 大事处理者这时会发觉想要读的数据已经乖乖地放在他供应的缓存区中,想怎么处理都行了。假如有需要,大事处理者还像之前一样发起此外一个写操作,和上面的几个步骤一样。现行做法开源C+开发框架ACElz3(DouglasSchmidtzetal.开发)供应了大量平台独立的底层并发支持类(线程、互斥量等).同时在更高一层它也供应了独立的几组C+类,用于实现ReaCtor及ProaCtor模式。尽管它们都是平台独立的单元,但他们都供应了不同的接口.ACEProaetO在MS-Windows上无论是性能还在健壮性都更胜一筹,这主要是由于WindOWS供应了一系列高效的底层异步APL4,5.(这段
9、可能过时了点吧)不幸的是,并不是全部操作系统都为底层异步供应健壮的支持。举例来说,很多Unix系统就有麻烦.因此,ACEReaCtor可能是Unix系统上更合适的解决方案.正由于系统底层的支持力度不一,为了在各系统上有更好的性能,开发者不得不维护独立的好几份代码:为Windows预备的ACEProactor以及为Unix系列供应的ACEReactor.就像我们提到过的,真正的异步模式需要操作系统级别的支持。由于大事处理者及操作系统交互的差异,为Reaetor和Poactor设计一种通用统一的外部接口是特别困难的。这也是设计通行开发框架的难点所在。更好的解决方案在文章这一段时,我们将尝试供应一种
10、融合了PrOaCtor和ReaetOr两种模式的解决方案.为了演示这个方案,我们将ReaCtOr稍做调整,模拟成异步的ProaCtor模型(主要是在大事分别器里完成本该大事处理者做的实际读写工作,我们称这种方法为“模拟异步”)。下面的示例可以看看read操作是如何完成的: 大事处理者宣称对读大事感爱好,并供应了用于存储结果的缓存区、读数据长度等参数; 调试者等待(比如通过select(); 当有大事到来(即可读),调试者被唤醒,调试者去执行非堵塞的读操作(前面大事处理者已经给了足够的信息了)。读完后,它去通知大事处理者。 大事处理者这时被知会读操作已完成,它拥有完整的原先想要猎取的数据了.我们
11、看到,通过为分别者(也就上面的调试者)添加一些功能,可以让ReaCtOr模式转换为Poacto模式。全部这些被执行的操作,其实是和Reaetor模型应用时完全全都的。我们只是把工作打散安排给不同的角色去完成而已。这样并不会有额外的开销,也不会有性能上的的损失,我们可以再认真看看下面的两个过程,他们实际上完成了一样的事情:标准的经典的ReaCtor模式: 步骤1)等待大事(Reactor的工作) 步骤2)发”已经可读“大事发给事先注册的大事处理者或者回调(Reactor要做的) 步骤3)读数据(用户代码要做的) 步骤4)处理数据(用户代码要做的)模拟的Poactor模式: 步骤1)等待大事(Pr
12、oactor的工作) 步骤2)读数据(看,这里变成成了让Proactor做这个事情) 步骤3)把数据已经预备好的消息给用户处理函数,即大事处理者(PrOaCtor要做的) 步骤4)处理数据(用户代码要做的)在没有底层异步1/0API支持的操作系统,这种方法可以帮我们隐蔽掉s。Cket接口的差异(无论是性能还是其它),供应一个完全可用的统一异步接口这样我们就可以开发真正平台独立的通用接口了。TProactor我们提出的TProaCtOr方案已经由TerabitP/L6公司实现了.它有两种实现:C+的和JaVa的.C+版本使用了ACE平台独立的底层元件,最终在全部操作系统上供应了统一的异步接口。T
13、Proactor中最重要的组件要数Engine和WaitStrategy了.Engine用于维护异步操作的生命周期;而WaitStrategy用于管理并发策略.WaitStrategy和Engine一般是成对消失的,两者间供应了良好的匹配接口.EngineS和等待策略被设计成高度可组合的(完整的实现列表请参照附录1)。TProactor是高度可配置的方案,通过使用异步内核API和同步UnixAPI(select()zpoll()zdevpoll(Solaris5.8+),port_get(Solaris5.10)zRealTime(RT)signals(Linux2.4),epoll(Linu
14、x2.6),k-queue(FreeBSD),它内部实现了三种引擎(POSIXAIO,SUNAIOandEmulatedAlO)并隐蔽了六类等待策略。TPrOaCtor实现了和标准的ACEProaCtor一样的接口。这样一来,为不同平台供应通用统一的只有一份代码的跨平台解决方案成为可能。EngineS和WaitStrategieS可以像乐高积木一样自由地组合,开发者可以在运行时通过配置参数来选择合适的内部机制(引擎和等待策略)。可以依据需求设定配置,比如连接数,系统伸缩性,以及运行的操作系统等。假如系统支持相应的异步底层APL开发人员可以选择真正的异步策略,否则用户也可以选择使用模拟出来的异步
15、模式。全部这一切策略上的实现细节都不太需要关注,我们看到的是一个可用的异步模型。举例来说,对于运行在SUnSoIariS上的HTTP服务器,假如需要支持大量的连接数,devpoll或者POrt_get()之类的引擎是比较合适的选择;假如需要高吞吐量,那使用基本SeIect()的引擎会更好。由于不同选择策略内在算法的问题,像这样的弹性选择是标准ACEReaCtOr/Proactor模式所无法供应的(见附录2)。在性能方面,我们的测试显示,模拟异步模式并未造成任何开销,没有变慢,反倒是性能有所提升。依据我们的测试结果,TProaetOr相较标签的ACEReactorffiUniLinu系统上有大约
16、10-35%性能提升,而在Windows上差不多(测试了吞吐量及响应时间)。性能比较(JAVA/C+/C#).除了C+,我们也在Java中实现了TProaCtor.JDK1.4中,Java仅供应了同步方法,像C中的select()7,8.JavaTPrOaCtOr基于Java的非堵塞功能(java.nio包),类似于C+的TProaCtOr使用了SeIeCt()引擎.图1、2显示了以bits/sec为单位的传输速度以及相应的连接数。这些图比较了以下三种方式实现的echo服务器:标准ACEReactor实现(基于RedHatLinux9.0)TProactorC+Java实现(MiCosoftWindows平台及RedHatv9.