| Roger Chen's profileLearningBlogLists | Help |
|
|
4/20/2006 Cindy 3.0b1 released修改记录:
终于发布beta1了,API结构已经稳定下来了,原来使用cindy 3.0 alpha版本的用户请尽快升级到beta版,以后的版本发布只会更新bug或增加功能,但是不会影响到API结构。
User Guide也同步升级,谢谢wei_cheng提出的很多好问题! 4/4/2006 CVS和Issue Tracker迁移到javaeye4/3/2006 Sourceforge的稳定性还真有待提高因为以前版本都是我一个人开发,所以代码都是使用本地的CVS,一来稳定,二来速度快。现在庄表伟加入后,我想还是把代码check到sourceforge的CVS上,这样协同比较方便。
可是自从我把Cindy 3.0a5版本的代码check到sourceforge上后,连续4天我都没有办法再连接上sourceforge的CVS服务(匿名用户的CVS服务一直可以,但是开发者的CVS一直没法连接上) :(发现没有CVS真是寸步难行。修改的地方越多担心就越大,无法提交,也无法恢复成CVS上的当前版本,慢慢的就不敢下手改代码了。
实在没办法,等了它四天都没有修复,只能恢复成使用本地CVS了。将已经确认的改动提交进去后,就松了一口气,开始放心大胆的改代码了:) 4/1/2006 Selector.select(timeout) throws NullPointerException occasionally (win)今天本想对Cindy进行一些效率上的优化,尽量减少对Selector.select的调用以及传递给Selector的句柄数量,没想到意外又触发了nio的一个Bug:在Windows平台上,Selector.select调用可能抛出空指针异常。
堆栈类似于:
我机器上有四个Java版本,在Sun JDK 1.4.2_05、Sun JDK 1.5.0_02、JRockit 1.5.0_04上都不会出现问题,只有在Sun JDK 1.5.0_06上面能重现Bug。反编译sun.nio.ch.WindowsSelectorImpl,可以看到其FdMap.remove中忘记判断空了(红色代码部分):
private MapEntry remove(SelectionKeyImpl selectionkeyimpl)
{ Integer integer = new Integer(selectionkeyimpl.channel.getFDVal()); MapEntry mapentry = (MapEntry)get(integer); if(mapentry.ski.channel == selectionkeyimpl.channel) return (MapEntry)remove(integer); else return null; } 到Sun的Bug database中一查,早已有人提交了该Bug,这个Bug目前只在mustang b75版本中得到了解决。
我看了看Sun以前版本的实现,虽然不会出现该Bug,但是恐怕会有其他的问题。Sun可能是想修改代码来解决原来的一些Bug,没想到引入了一个新Bug:(虽然Sun bug database中已经有了解决方案,但是绝大部分的用户应该都不会自己去修改Sun的源码然后打包的。
目前发布的这个Cindy版本不会受到这个Bug的影响。鉴于Sun目前还没有将该Bug的修正放到Java 5.0的升级包中,所以我也暂时取消对Cindy效率上的一些优化(由于这些优化才触发了NIO的Bug),等到它升级包发布出来后再另行考虑。 3/27/2006 Cindy 3.0a5 released修改记录:
比较重要的几点改进:
比如如下代码:
第一行代码使得Future完成事件被触发,第二行代码使得SessionFilterChain上的sessionStarted事件被触发。看上去没有任何问题,是吗? 可是,如果加入这么一个FutureListener:
在使用DirectDispatcher的情况下,事件的触发顺序就变成了: Start future completed(上面的第一行代码) --> session close --> session closed --> session started(上面的第二行代码),出现了明显的错误。 所以这次Cindy 3.0a5中把所有这种顺序派发的事件全部改在Dispatcher内完成,比如上面的代码就改成了: // keep dispatch order public void run() { 可以参考DirectDispatcher中的代码,就能了解为什么这么做就能保持事件分发顺序。 3/25/2006 目标最近常被问到的一个问题是:做高效率的网络应用都是用C/C++,为什么要用Java?我也一直在思考这个问题,这牵涉到Cindy的定位,以及将来的发展方向。
如果存在一个这样的假设:C/C++和Java在网络处理方面拥有接近的运行效率,你会选择用什么语言开发网络应用?我的答案会是Java。为什么?因为对于大部分普通程序员而言(某些C/C++高手除外),在业务逻辑的开发效率上,Java要超出C/C++许多,这也正是Java流行起来的原因。
但是遗憾的是,这个假设目前还不存在:( JSR 203提议在Java中加入对操作系统本身异步IO的支持,但没有人推动;IBM推出过aio4j,不开源,而且在2004年就停止了更新。所以目前在你所看到的现实中,这类应用仍旧是C/C++的天下。
虽然假设并不存在,但需求是切切实实存在的:大家对Java网络方面的效率有着更高的期望。比如Tomcat,在它的5.5版本中有基于ARP的JNI实现;比如Resin以及其他很多商业WEB服务器,都有JNI的实现。但是遗憾的是,这些JNI并不是一个独立的网络框架,所以虽然这些人都在自己发明轮子,但是其他人很难用上他们发明好的轮子。
既然有需求,而且需求无法得到满足,那么这里就有机会。
做为一个比较资深的Java程序员,做为一个Java网络框架的开发者,我所关心的是:能不能让这个假设成为现实?或者能不能缩小它们的差距,给Java这边加些砝码?这就是Cindy的长远目标。目前虽然我的能力离实现这个目标还有差距,不过只要目标清晰,一直往前走,总是能实现的。
希望能有志同道合者和我一起来实现这个目标:) 3/14/2006 Cindy 3.0a5计划加入Qos控制本来打算下一个版本就b1了,不过这两天发现一个非常值得改进的地方:发送的Qos控制,而且该改动会影响一些现有的API,如send(Object, int)/flush(Object,int)等等,所以还是下一个版本还会是alpha版本。
在a4的实现中,Qos控制是由一个PriorityQueue来完成的,根据不同的优先级权重来调整发送顺序。但是该实现只是一种特定的Qos控制,我的计划是把Qos控制这部分分离出来,可以由用户来进行设置。 比如Concurrent包中有Delayed接口和DelayQueue实现,DelayQueue能够让添加进去的Delayed对象在指定时间后才能被取出,这种Qos控制可以用于控制包发送间隔时间。对于类似WEB、FTP这类应用,发送速度是越快越好,Qos要求可能不明显;但对于流媒体这类应用,并不是发送速度越快越好,只要保证流媒体能够流畅观看即可,发送速度过快反而可能导致其他人无法流畅观看,这种Qos控制就非常有用。
但是Concurrent包的Queue/BlockingQueue接口并不能直接用于Cindy的Qos控制,因为它们只提供了阻塞和轮询两种处理方式,这种开销对Cindy来说是不可接受的,所以需要实现队列的回调方法。另外可配置性的Qos控制怎么与现有的发送API、SessionFilter、Packet接口等结合起来,仍在考虑之中,希望能想出一个比较可行的解决方法来。 3/11/2006 Cindy 3.0a4 效率测试硬件环境:
软件环境:
测试工具:
测试结果
Echo web server:
File web server (small file 100 bytes):
File web server (small file 100 bytes, keep alive):
<P
File web server (large file 947111 bytes):
总结 先解释一下为什么Cindy 3.0a4的测试会有两种:一种是基于SocketSessionAcceptor的,一种是基于ServerSocketChannelSession的。在开发机器上测试时,这两种类型的测试结果有较大的差距,基于Acceptor的测试实际上同时有两个线程在运行,一个线程不停的accept,然后另一个核心线程做相应的处理;而基于Session的测试实际上只有一个线程在运行,就是核心线程既负责accept,也负责处理。在赛扬2.4的机器上,第一种的测试成绩大约为650#/sec,而第二种的测试成绩大约能到900#/sec。鉴于这两者在开发机器上的性能差距,所以在服务器上也做了这两种测试。 另外,由于这种测试绝大部分时间都是消耗在网络I/O上面,业务逻辑的处理用时非常少,所以只有在使用DirectDisptcher时才拥有最好的响应时间,多开线程会使响应时间大幅下降。也正是这个原因,前两种类型在开发机上的测试结果有如此大的差距。不过由于这次测试机器的CPU是XEON,双核,所以这两种类型的测试结果差距不明显。
在Echo web server的测试中,MINA 0.9.2在完成一个请求后就不再相应后续的请求了,可能是代码中存在的一些Bug所导致,所以这次测试对MINA来说其实稍微有些不公平,我会等它在后续的版本修正了这个问题后再进行一次测试;本来在开发机上QuickServer的测试结果和Cindy的测试结果非常接近,在blocking模式下,性能甚至稍微超出Cindy 3.0a4,没想到放到服务器上测试时会有这么大的差距;Cindy 2.4.4依旧傻快傻快,呵呵,不过它的模型没有Cindy 3.0这么容易扩充,而且Cindy 3.0在这种场景下和它的差距也是非常小的,所以还是推荐尽早升级到Cindy 3.0。 基于小文件的测试,Resin和Tomat遥遥领先,阻塞I/O+线程池的效率充分发挥了出来。并且这种压力测试速度又快,一个连接非常短的时间就能被处理完,正好是阻塞I/O+线程池的优势所在。在concurrent较小的情况下,这种优势更加明显,在concurrent逐渐增大后,阻塞I/O和非阻塞I/O处理效率上就越来越接近了。在这个测试中Jetty的结果比较令人失望。 Keep alive的连接测试中,基于Session的测试耗时太长被中途放弃,怀疑是由于单线程运行+Keep alive造成了始终在处理第一个连接所造成,这个还有待证实;Tomcat的表现更是突出,而在这个场景中本来应该是非阻塞I/O的强项所在。不过最大的concurrent也只到1000,在真实的环境中,如果concurrent持续增大,cindy的优势应该会更加突出才对。 大文件的测试类似于Keep alive的测试,这种场景应该也是非阻塞I/O的优势场景。在该场景中,cindy总算体现了非阻塞I/O的优势,而tomcat的表现就有点失望了,失败数也太多了一点……我研究过Tomcat的源代码,没采用JNI的话它是基于阻塞I/O的实现,只能通过SO_TIMEOUt来中断对数据的读取和写入,所以它在运行时会通过计算服务器的负载来设置相应的SO_TIMEOUT,负载小于33%情况下采用默认的SO_TIMEOUT,33%到66%之间SO_TIMEOUT减半,66%到90%之间,SO_TIMEOUT变为默认的1/3,超过90%则急速降为默认的5%。我怀疑在大文件的测试当中,由于服务器线程负载太大,所以导致超时时间变为默认的5%,从而造成了这么多的失败数。
总体来讲,在基于NIO的异步I/O框架中,我认为Cindy的效率应该算是很不错的了;阻塞I/O+线程池的做法在并发量少、连接时间短的应用中比NIO有着更高的效率,但是随着并发量的上升,两者的差距会逐渐缩小(再上升会不会反超?未经证实……)。但Java的网络层效率还是和C/C++的实现还是有着明显差距,高性能网络应用的首选还是C/C++。 Cindy 3.0a4 released修改记录:
等待一段时间后准备进入beta阶段。
Update: 发布后才发现http server的ant脚本有些问题。如果各位要测试http server,要么把ant脚本修改成fork一个新的java进程,要么命令行指定ant -Dparam=-acceptor http-server来运行。 非常抱歉! 3/7/2006 又发现NIO的一个小Bug按照NIO的规范来说,对于非阻塞Channel,如果当前write方法无法发送任何字节,则应该返回0,而不是抛出异常。 以前我就发现了它的一个Bug:在Java 1.4的虚拟机上,SocketChannel的write(ByteBuffer[])的JNI操作实现中没有检测某个返回值,抛出了java.io.IOException: A non-blocking socket operation could not be completed immediately异常。而这种情况下本应该返回0的。好在这个Bug不影响大局,一般只要绕过对该方法的调用即可,况且这个Bug已经在Java 5.0中得到了解决。 不过今天又发现了它的一个Bug:SocketChannel的write(ByteBuffer)的JNI操作,在Window的实现上没有检测WSAENOBUFS的返回值,抛出了java.io.IOException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full异常。而这种情况下也本应该返回0的。 应该很少会有应用会触发这个异常,我之所以发现了这个异常也是运气:) 在Cindy简单的HttpServer示例中,图个方便简单的使用了内存映射文件,而且不管文件大小都是全部映射(我是示例Cindy的用法,千万不要在生产系统中这么做)。晚上测试一个200多M文件的下载,我的内存肯定是足够的,所以内存映射没有出现任何问题,但是一次性通过write方法发送200多M的Direct ByteBuffer,呵呵,这个现象就能重现出来了:)
实际应用中应该不会有人象我这么玩的,呵呵,所以这个Bug影响倒也不大。对于应用来说,一方面是不要一次性发送这么大的数据,另一方面是增大Socket的发送缓冲区大小。对于Cindy而言,要对应用屏蔽这个Bug,即使应用发送这么大的数据也不要触发这个异常,倒是有一个简单的方法:把传递给write方法的ByteBuffer控制在一定的字节数内。 即使应用要Cindy发送一个200M的ByteBuffer,也可以控制成每次调用write方法时,只传给它一个1M大的ByteBuffer,分200次发送即可。这样就可以简单的对应用屏蔽这个Bug了。 3/6/2006 Cindy 3.0a4 configurationGlobal configuration
Buffer configuration
Dispatcher configuration
Session configuration
Acceptor configuration
For example, to enable jmx, use direct buffer, set dispatcher keep alive time to 10s and enable acceptor reuse address, you can specify configuration in system environment :
or you can put cindy.properties on your classpath:
3/2/2006 Cindy 3.0a3 user guide releasedUser Guide中的示例都是非常简单的示例,起个入门的作用。有了这个引子,再看看cindy源代码example目录下的示例,应该很容易就能上手。
希望各位多提意见,这样才好根据你们的意见来做改进:)
a4的主要目标是提高Cindy的易用性,比如通过运行期参数配置的Dispatcher线程池大小等等。如无意外的话应该两个礼拜后会release出来。
BTW:我这边好像无法访问sourceforge各个项目的主页,即xxx.sourceforge.net。难道又被封了?
Update: 晚上将原示例中简单的Echo Http Server扩充成为一个相对完整的基于文件的Http Server,然后又测试了一下效率,效率上居然也没有太多的降低。原来Requests Per Second差不多是4200#/sec,现在Requests Per Second差不多是3600#/sec。看来等a4版发布后,可以做一个比较完整的评测了,不仅可以和MINA等比较,还可以和现有的一些HttpServer进行比较(当然,它们的实现要完整的多,对它们本身的效率会有些影响,这点要考虑进去) 2/27/2006 Cindy 3.0a3 released下载地址: http://sourceforge.net/projects/cindy 修改记录:
最大的改进就是加入了SSLFilter以及反向分发xxxSend/Sent事件。 SocketSessionAcceptor中所增加的属性也是在实现网络服务器时经常要用到的,至于SocketSession、DatagramSession中经常要设置的属性由于数量太多,还是通过暴露出来的Socket和DatagramSocket来设置比较方便一点。
SSLFilter还有一个小缺陷,由于Session没有提供beforeClose的一个事件出来,因此在Session关闭前不能发送SSL的close message。这样服务器端可能会抛出一个异常: javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack? 这种实现不是太完美,不过完美的实现只能通过继承已有的SocketSession实现来解决,在close前发送close message,但这就缺乏扩展性。目前权衡下来暂时采用Filter的设计,如果应用真的在意这个close message,那么可以通过组合SSLFilter和SocketSession实现来加以解决。 2/17/2006 SessionFilter顺序在实现Cindy 3.0a3 SSLFilter的过程中,发现一个有意思的问题:不同类型的SessionFilter对于过滤事件的顺序有着不同的要求。
假设有这么一个场景,通过SSL连接接收和发送数据,并统计所有的网络流量。
对于接收而言,SessionFilter的顺序应该是:
这样统计到的是真实的接收流量,应用拿到的也是解码后的数据。
对于发送而言,SessionFilter的顺序则应该是:
可以看到,对于发送和接收而言,这两个Filter的顺序是相反的。如果这个场景再复杂一些,数据发送前需要先进行压缩,接收到数据后再解压缩,那么从发送到接收的整个流程是(红色是发送,蓝色是接收):
可以看到在这些场景中,Filter在处理发送和接收事件上顺序是相反的。而在以前Cindy的设计里面,对于所有事件的分发都是按照相同的顺序,这可能造成困惑。
目前我的想法是,针对应用层的Filter,将所有的send/sent事件分发按照Filter添加的顺序反向分发,其他的事件则按照添加顺序分发。但这种做法缺乏灵活性,因为这暗示了所有Filter需要的就是这种顺序(虽然目前除了系统级的Filter外,我还没想到什么样的Filter处理发送和接收事件的顺序需要保持一致)。另一种做法可以把所有事件都拆分开,指定每个事件的处理顺序,但这种做法言应该会过于繁琐。
目前Cindy内系统级Filter对发送和接收事件分发的顺序需要保持一致。如果在上面这个流程图中加入系统级Filter,那么整个顺序是这样的(红色代表系统级Filter)
虽然这种全部反向分发接收事件的做法还有待商榷,但可以肯定的是,该做法比原来所有顺序一致的做法要好,因为反向分发至少覆盖了大部分的应用类型。
如果你来设计,你会怎么做呢? 2/13/2006 SessionDispatcherJerry来信问了一些有关SessionDispatcher的问题,在征得其同意后,将来信和回信贴在这里,希望能给大家一些帮助。
来信
你好,看了你的cindy觉得很有意思,看到他一直在进步,也很让人高兴。
看了新出的cindy3.0a2里面的代码,和3.0a1做了一下比较,发现了一些不同,所以想请教一下你的想法
你的SessionDispatcher里面启动的是只有一个线程的线程池,很想了解这里能把线程池中的线程数目调大吗?
从我的理解应该是可以的,这样就形成了处理NIO常用的1-N的线程模型,不知道理解是否有误?
在Cindy3.0a1的HSHAReactor的start方法里面有如下的代码 private synchronized void start() {
if (selectThread != null) return; int minPool = CindyConstants.getMinThreadPoolSize();
int maxPool = CindyConstants.getMaxThreadPoolSize(); // keep event dispatched in order if (minPool == 0 && maxPool == 0) dispatcher = new DirectExecutor(); else //Start a seperate dispatcher thread, maybe this will become a thread pool. dispatcher = Executors.newSingleThreadExecutor(threadFactory); lastSelectTime = new ElapsedTime(); 这个dispatcher处理的都是selector相关的东西。但是在Cindy3.0a2里面这部分代码去掉了,是发现了什么线程问题吗?
3.0a1和a2的start方法上都有个synchronized关键字,里面启动了一个线程。
这样的话,在这个内部线程死掉之前,这个方法是不能被别的线程访问的,对吗?觉得这样的处理方法很tricky,呵呵。
请不吝赐教,谢谢!:)
回信
Jerry,
很高兴你对该项目抱有浓厚的兴趣,这里就你提出的问题做一些交流。
1.SessionDispatcher线程数的调节 SessionDispatcher的存在的目的并不是为了效率以及可扩展性,它存在的目的仅仅是为了保证结果的正确性,所以线程数没有调大的必要。 Cindy中同时存在着同步方法和异步方法。异步的比如Listener、Filter一系列机制都是用于处理异步情况的,在相应的事件完成后再通知应用;同步的比如Future.complete(),会在该任务完成后才从该方法中返回。
这里就牵涉到的问题就是:因为核心是异步的,如果应用在核心线程中调用了同步方法,则该同步方法将永远无法完成。
所以SessionDispatcher的作用就是使得真正的核心线程永远也到达不了应用层,所有应用层的事件都是由SessionDispatcher中的线程来发送。同时,为了保证同步方法不阻塞SessionDispatcher,一旦应用层调用了同步方法,SessionDispatcher就会通过内置的线程池来选择另一个线程发送消息。
举个例子。比如同时开启了10个Session,其中一个Session在sessionStarted事件中进行数据发送,并调用了Future.complete方法。
session.addSessionHandler(new SessionHandlerAdapter() {
public void sessionStarted(...) { session.send(someObj).complete(); } }); 如果是核心线程进行事件的发送。那么调用complete后,这时并没有开始发送,所以线程被阻塞;并且由于核心线程被阻塞,真正的网络操作将永远无法执行,只能被一直阻塞。
如果只是单线程进行事件发送。那么调用complete方法后,事件发送线程被阻塞,但核心线程还是可以完成网络操作,并把操作完成的事件添加到事件发送队列中。但是由于事件发送线程被阻塞,它也无法得到事件完成的消息,它也会一直阻塞在这里。并且,即使有办法恢复事件发送线程,但在它阻塞的过程中,其他9个Session的消息也没有办法发送,这会极大的降低吞吐量。
目前的做法还是单线程分发事件,但一发现应用调用阻塞操作,就通过一个线程池获取另一个线程继续后面的事件分发;等到原线程从阻塞操作中恢复,再把线程放到线程池中。是Lead-Follows模型的应用。
2.Reactor的start方法上使用synchronized关键字 在该方法中使用synchronized关键字,并不会使在该方法中开启的线程也必须要拥有该锁。所以内部线程不死掉,该方法也能被调用。
3.Reactor的线程池调节
a2对这个的改动并不是线程问题,而是实际测试中发现这些代码并没有意义,不管在单CPU还是多CPU的情况下,效率只有降低,而没有提高,所以才去掉了这部分的代码。
至于1-N的线程模型,由于Filter机制的提供,应用可以根据自己的需要做更细的调节。比如通过自己实现的Executor,再加上ExecutorFilter做N个分派线程;或者仅仅对messageReceived事件进行池化分派,等等。这些完全取决于应用自身的需要。但是需要注意的是,一旦通过自己实现的线程池进行分派后,在进行同步方法的调用时就要注意尽量不影响其他异步操作的进行。 2/10/2006 Cindy 3.0a2 released修改记录:
稳定性相对a1来说有很大的增强,不客气一点的说,比MINA也稳定的多。添加的第一和第二项功能也很有意思。 增加了JMX支持,比如如果运行在Java 5.0下,可以在运行时指定-Dcom.sun.management.jmxremote -Dnet.sf.cindy.useJmx,然后通过jconsole观察Cindy的运行状态。 增加了流量控制,使得内存溢出现象得到了控制。比如以前本机对本机发送文件,写文件操作相对网络接收速度慢很多,导致了接收到的Buffer在内存中越堆越多,造成内存溢出;还有软件调试时,在应用层设断点调程序,后面的网络处理还在继续,也会容易造成内存溢出。加入流量控制后,只要队列满了,后面的网络操作也慢了下来。虽然总体吞吐量是减少了,但是保证了系统的稳定性。 这些可以调整的参数,都可以参见net.sf.cindy.util.CindyConstants中的声明。
a3版本计划加入对SSL的支持,以及加入更多的JMX管理功能。当然,Bug修正是少不了的了:) 1/27/2006 Cindy 3.0 效率测试客户端环境:
服务器环境,客户端和服务器在同一台机器上,不带任何参数运行:
测试方法:
结果:
参数对比:
简单结论: 该测试仅代表某一特定并发量小且连接时间短的场景。在该场景下,Cindy 3.0的处理效率大概比MINA高出20%左右,而Cindy 2.x的处理效率比3.0要高出27%,这让我有点惊讶。 我猜测可能在这么简单的场景下,3.0的Buffer Cache完全发挥不了作用,反而2.x中每个连接一个固定的缓存节约了从Buffer Cache中查找和释放的开销。我也测试了Tomcat 5.5的表现,结果和Cindy 2.2.4比较接近。Tomcat可是完整的HTTP实现,不像这些半吊子的HttpServer,而且据我的了解Tomcat 5.5还是采用的是阻塞模型。实际上在并发量不大和长时间连接的情况下,阻塞模型比非阻塞模型效率更好,所以不要简单的认为采用了nio效率就一定比io要强:) 这个结果也表明,Cindy 3.0目前的版本还有非常大的优化空间:) 该测试也说明了要慎用MINA的StreamIoHandler,该Handler把异步处理完全变成了同步处理,并且是用非阻塞来模拟阻塞,效率应该比不上传统的Java I/O模型。
Update: 以上的结果在一台赛扬2.4的机器上测试得到,服务器和客户端都在同一台机器上,只有相对参考价值。在服务器和客户端均为XEON 3G的情况下,测试结果约为4000 Requests per second。 1/25/2006 cindy 3.0a1上传到sourceforge1/24/2006 Cindy 3.0a1 released由于GFW的成功封锁,没法上传到sourceforge,
简单示例:
下载:
更新:
Alpha版本,肯定还有不少Bug和可以优化的地方,希望各位多加反馈:) 12/24/2005 Cindy 3.0中的Buffer设计俗话说兵马未动,粮草先行,Cindy 3.0版本连影子还没见着,Blog倒是放出了好几篇……
为了以示区分,nio的ByteBuffer我简称为NB,cindy的Buffer我简称为CB。
现在对NB的理解比上次写Redesign Buffer这篇Blog时要更深入一些,所以在CB的设计上观念有了一些变化:
如果CB容量是不固定的,则在自动扩展时需要进行内存拷贝,并且在实现中判断是否需要进行扩展会有许多额外的判断,还无法精确匹配应用的需求,降低了效率;但是对于某些特定的应用,该功能确实能带来一些便利。 所以在目前的设计中,CB的自动扩展并不是CB体系的一个基本功能,仅仅为一个特定实现。除了该实现外,其余的CB都不是自动扩展的,还是固定的capacity。
在我前面一篇Blog中可以看出,大部分应用在如果没有自己实现NB pool,正常情况下应该实例化成Non-direct NB,可是Non-direct NB聚集读写要比非聚集读写效率低。所以WrappedBuffer看上去节约了内存拷贝时间,可实际在写入和读取的时候要实例化不缓存的临时Direct NB,反而降低了速度。
这种特殊类型的Buffer其实并不是为应用准备的,而是为Cindy中的Session MessageRecognizer准备的。 在Cindy 2.x中,读数据的流程是(2.x还没有CB的设计):
可以看到,对于应用直接从网络中读取数据,中间多了一层内存拷贝(先拷贝到Session内部缓存中,再到应用)。但是通过这一间接层,把所有的网络情况处理和异常处理都交给框架来处理了,减少了应用的负担,所以多出来的这一层的好处大于坏处。 在Cindy 3.x中,由于Filter机制,读数据的流程是:
可以看到,相对Cindy 2.x来说,引入了许多间接层。那么效率能否不降低呢? 首先在默认情况下,内置的Filter不会转换读出的CB块(除非你自己加入特定的Filter,如SSLFilter,把收到的数据包解密给普通数据包),所以在Filter转换后的CB块和原来的CB块是同一个,没有额外的开销。 Session内部的CB将收到的一系列CB块重组(比如TCP连接,第一次收到了abc,MessageRecognizer没有识别出来,第二次收到了def,这次收到的def应该加在abc后面,即abcdef,再让MessageRecongizer识别),如果还是采用原始的设计,在这个地方就多了一次内存拷贝,而且如果缓存容量不够的话,还得重新生成一个容量足够的CB,再把收到的一系列CB放进去,效率肯定会有所降低。这个时候就是LinkedBuffer发挥威力的地方。 LinkedBuffer把收到的CB包连接起来,对外看上去好像只有一个CB,不需要进行内存拷贝,也不会有缓存容量不够的情况出现(因为仅仅是把若干个CB链接起来)。MessageRecongizer直接从LinkedBuffer中读入数据,等到某CB块上的数据被Message读入完毕后,再把该CB块从链上去掉即可。 所以即使中间多了两层,但是效率仍然不会降低。更何况中间多加的两层具有非常好的可扩展性,极端情况下,你甚至可以在Filter这一层就把接收到的CB直接拿出来使用,而不传给后面的Filter,这样连Cindy 2.x中那一次内存拷贝都省了:)
在最后说一个不是太好的消息。原计划12月底发布3.0 a1版,现在看来是来不及了,甚至连Reactor模型我都还没由HSHA改成LF……况且月底圣诞、元旦两个比较大的节日,还是要多去享受享受生活的…… 在此向各位关注Cindy的朋友们致歉,这个版本应该会在1月中旬出来的,到时还请多多提一些建设性意见:) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|